Revert "Enable usage of experimental incremental compiler for web (#43576)" (#44396)

This reverts commit e5d6d92498.
This commit is contained in:
Jonah Williams 2019-11-07 16:32:20 -08:00 committed by GitHub
parent e5d6d92498
commit 7535ef735f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 317 additions and 978 deletions

View file

@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart';
Future<void> main() async {
await task(createWebDevModeTest(WebDevice.webServer, false));
await task(createWebDevModeTest());
}

View file

@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart';
Future<void> main() async {
await task(createWebDevModeTest(WebDevice.webServer, false));
await task(createWebDevModeTest());
}

View file

@ -1,10 +0,0 @@
// Copyright 2019 The Chromium Authors. 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:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart';
Future<void> main() async {
await task(createWebDevModeTest(WebDevice.chrome, true));
}

View file

@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart';
Future<void> main() async {
await task(createWebDevModeTest(WebDevice.webServer, false));
await task(createWebDevModeTest());
}

View file

@ -20,21 +20,12 @@ const String kFirstRecompileTime = 'FirstRecompileTime';
const String kSecondStartupTime = 'SecondStartupTime';
const String kSecondRestartTime = 'SecondRestartTime';
abstract class WebDevice {
static const String chrome = 'chrome';
static const String webServer = 'web-server';
}
TaskFunction createWebDevModeTest(String webDevice, bool enableIncrementalCompiler) {
TaskFunction createWebDevModeTest() {
return () async {
final List<String> options = <String>[
'--hot', '-d', webDevice, '--verbose', '--resident', '--target=lib/main.dart',
'--hot', '-d', 'web-server', '--verbose', '--resident', '--target=lib/main.dart',
];
int hotRestartCount = 0;
final String expectedMessage = webDevice == WebDevice.webServer
? 'Recompile complete'
: 'Reloaded application';
final Map<String, int> measurements = <String, int>{};
await inDirectory<void>(flutterDirectory, () async {
rmTree(_editedFlutterGalleryDir);
@ -47,8 +38,6 @@ TaskFunction createWebDevModeTest(String webDevice, bool enableIncrementalCompil
<String>['packages', 'get'],
environment: <String, String>{
'FLUTTER_WEB': 'true',
if (enableIncrementalCompiler)
'WEB_INCREMENTAL_COMPILER': 'true',
},
);
await packagesGet.exitCode;
@ -57,26 +46,16 @@ TaskFunction createWebDevModeTest(String webDevice, bool enableIncrementalCompil
flutterCommandArgs('run', options),
environment: <String, String>{
'FLUTTER_WEB': 'true',
if (enableIncrementalCompiler)
'WEB_INCREMENTAL_COMPILER': 'true',
},
);
final Completer<void> stdoutDone = Completer<void>();
final Completer<void> stderrDone = Completer<void>();
final Stopwatch sw = Stopwatch()..start();
bool restarted = false;
process.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
// TODO(jonahwilliams): non-dwds builds do not know when the browser is loaded.
if (line.contains('Ignoring terminal input')) {
Future<void>.delayed(const Duration(seconds: 1)).then((void _) {
process.stdin.write(restarted ? 'q' : 'r');
});
return;
}
if (line.contains('To hot restart')) {
// measure clean start-up time.
sw.stop();
@ -84,10 +63,9 @@ TaskFunction createWebDevModeTest(String webDevice, bool enableIncrementalCompil
sw
..reset()
..start();
process.stdin.write('r');
return;
process.stdin.write('R');
}
if (line.contains(expectedMessage)) {
if (line.contains('Recompile complete')) {
if (hotRestartCount == 0) {
measurements[kFirstRestartTime] = sw.elapsedMilliseconds;
// Update the file and reload again.
@ -102,10 +80,9 @@ TaskFunction createWebDevModeTest(String webDevice, bool enableIncrementalCompil
sw
..reset()
..start();
process.stdin.writeln('r');
process.stdin.writeln('R');
++hotRestartCount;
} else {
restarted = true;
measurements[kFirstRecompileTime] = sw.elapsedMilliseconds;
// Quit after second hot restart.
process.stdin.writeln('q');
@ -142,35 +119,24 @@ TaskFunction createWebDevModeTest(String webDevice, bool enableIncrementalCompil
flutterCommandArgs('run', options),
environment: <String, String>{
'FLUTTER_WEB': 'true',
if (enableIncrementalCompiler)
'WEB_INCREMENTAL_COMPILER': 'true',
},
);
final Completer<void> stdoutDone = Completer<void>();
final Completer<void> stderrDone = Completer<void>();
bool restarted = false;
process.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
// TODO(jonahwilliams): non-dwds builds do not know when the browser is loaded.
if (line.contains('Ignoring terminal input')) {
Future<void>.delayed(const Duration(seconds: 1)).then((void _) {
process.stdin.write(restarted ? 'q' : 'r');
});
return;
}
if (line.contains('To hot restart')) {
measurements[kSecondStartupTime] = sw.elapsedMilliseconds;
sw
..reset()
..start();
process.stdin.write('r');
return;
process.stdin.write('R');
}
if (line.contains(expectedMessage)) {
restarted = true;
measurements[kSecondRestartTime] = sw.elapsedMilliseconds;
if (line.contains('Recompile complete')) {
measurements[kSecondRestartTime] = sw.elapsedMilliseconds;
process.stdin.writeln('q');
}
print('stdout: $line');

View file

@ -112,13 +112,6 @@ tasks:
stage: devicelab
required_agent_capabilities: ["linux/android"]
web_incremental_test:
description: >
Verify that the experimental frontend server support is functional.
stage: devicelab
required_agent_capabilities: ["linux/android"]
flaky: true
flutter_gallery_ios__compile:
description: >
Collects various performance metrics of compiling the Flutter

View file

@ -28,7 +28,7 @@ enum Artifact {
platformLibrariesJson,
flutterPatchedSdkPath,
frontendServerSnapshotForEngineDartSdk,
/// The root directory of the dart SDK.
/// The root directory of the dartk SDK.
engineDartSdkPath,
/// The dart binary used to execute any of the required snapshots.
engineDartBinary,

View file

@ -8,8 +8,7 @@ import 'package:build_daemon/client.dart';
import 'package:dwds/dwds.dart';
import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart' as vmservice;
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
hide StackTrace;
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart' hide StackTrace;
import '../application_package.dart';
import '../base/async_guard.dart';
@ -17,21 +16,16 @@ import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/os.dart';
import '../base/terminal.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../convert.dart';
import '../devfs.dart';
import '../device.dart';
import '../features.dart';
import '../globals.dart';
import '../project.dart';
import '../reporting/reporting.dart';
import '../resident_runner.dart';
import '../run_hot.dart';
import '../web/chrome.dart';
import '../web/devfs_web.dart';
import '../web/web_device.dart';
import '../web/web_runner.dart';
import 'web_fs.dart';
@ -40,24 +34,14 @@ import 'web_fs.dart';
class DwdsWebRunnerFactory extends WebRunnerFactory {
@override
ResidentRunner createWebRunner(
FlutterDevice device, {
Device device, {
String target,
@required bool stayResident,
@required FlutterProject flutterProject,
@required bool ipv6,
@required DebuggingOptions debuggingOptions,
}) {
if (featureFlags.isWebIncrementalCompilerEnabled) {
return _ExperimentalResidentWebRunner(
device,
target: target,
flutterProject: flutterProject,
debuggingOptions: debuggingOptions,
ipv6: ipv6,
stayResident: stayResident,
);
}
return _DwdsResidentWebRunner(
return ResidentWebRunner(
device,
target: target,
flutterProject: flutterProject,
@ -69,9 +53,8 @@ class DwdsWebRunnerFactory extends WebRunnerFactory {
}
/// A hot-runner which handles browser specific delegation.
abstract class ResidentWebRunner extends ResidentRunner {
ResidentWebRunner(
this.device, {
class ResidentWebRunner extends ResidentRunner {
ResidentWebRunner(this.device, {
String target,
@required this.flutterProject,
@required bool ipv6,
@ -85,27 +68,22 @@ abstract class ResidentWebRunner extends ResidentRunner {
stayResident: stayResident,
);
final FlutterDevice device;
final Device device;
final FlutterProject flutterProject;
DateTime firstBuildTime;
// Only the debug builds of the web support the service protocol.
@override
bool get supportsServiceProtocol =>
isRunningDebug && device.device is! WebServerDevice;
bool get supportsServiceProtocol => isRunningDebug && device is! WebServerDevice;
@override
bool get debuggingEnabled =>
isRunningDebug && device.device is! WebServerDevice;
bool get debuggingEnabled => isRunningDebug && device is! WebServerDevice;
WebFs _webFs;
ConnectionResult _connectionResult;
StreamSubscription<vmservice.Event> _stdOutSub;
bool _exited = false;
WipConnection _wipConnection;
vmservice.VmService get _vmService =>
_connectionResult?.debugConnection?.vmService;
vmservice.VmService get _vmService => _connectionResult?.debugConnection?.vmService;
@override
bool get canHotRestart {
@ -117,8 +95,7 @@ abstract class ResidentWebRunner extends ResidentRunner {
String method, {
Map<String, dynamic> params,
}) async {
final vmservice.Response response =
await _vmService.callServiceExtension(method, args: params);
final vmservice.Response response = await _vmService.callServiceExtension(method, args: params);
return response.toJson();
}
@ -138,7 +115,7 @@ abstract class ResidentWebRunner extends ResidentRunner {
}
await _stdOutSub?.cancel();
await _webFs?.stop();
await device.device.stopApp(null);
await device.stopApp(null);
if (ChromeLauncher.hasChromeInstance) {
final Chrome chrome = await ChromeLauncher.connectedInstance;
await chrome.close();
@ -157,15 +134,13 @@ abstract class ResidentWebRunner extends ResidentRunner {
return printHelpDetails();
}
const String fire = '🔥';
const String rawMessage =
' To hot restart changes while running, press "r". '
'To hot restart (and refresh the browser), press "R".';
const String rawMessage = ' To hot restart changes while running, press "r". '
'To hot restart (and refresh the browser), press "R".';
final String message = terminal.color(
fire + terminal.bolden(rawMessage),
TerminalColor.red,
);
printStatus(
'Warning: Flutter\'s support for web development is not stable yet and hasn\'t');
printStatus('Warning: Flutter\'s support for web development is not stable yet and hasn\'t');
printStatus('been thoroughly tested in production environments.');
printStatus('For more information see https://flutter.dev/web');
printStatus('');
@ -174,202 +149,18 @@ abstract class ResidentWebRunner extends ResidentRunner {
printStatus('For a more detailed help message, press "h". $quitMessage');
}
@override
Future<void> debugDumpApp() async {
try {
await _vmService?.callServiceExtension(
'ext.flutter.debugDumpApp',
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugDumpRenderTree() async {
try {
await _vmService?.callServiceExtension(
'ext.flutter.debugDumpRenderTree',
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugDumpLayerTree() async {
try {
await _vmService?.callServiceExtension(
'ext.flutter.debugDumpLayerTree',
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugDumpSemanticsTreeInTraversalOrder() async {
try {
await _vmService?.callServiceExtension(
'ext.flutter.debugDumpSemanticsTreeInTraversalOrder');
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugTogglePlatform() async {
try {
final vmservice.Response response = await _vmService
?.callServiceExtension('ext.flutter.platformOverride');
final String currentPlatform = response.json['value'];
String nextPlatform;
switch (currentPlatform) {
case 'android':
nextPlatform = 'iOS';
break;
case 'iOS':
nextPlatform = 'android';
break;
}
if (nextPlatform == null) {
return;
}
await _vmService?.callServiceExtension('ext.flutter.platformOverride',
args: <String, Object>{
'value': nextPlatform,
});
printStatus('Switched operating system to $nextPlatform');
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
try {
await _vmService?.callServiceExtension(
'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder');
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugToggleDebugPaintSizeEnabled() async {
try {
final vmservice.Response response =
await _vmService?.callServiceExtension(
'ext.flutter.debugPaint',
);
await _vmService?.callServiceExtension(
'ext.flutter.debugPaint',
args: <dynamic, dynamic>{
'enabled': !(response.json['enabled'] == 'true')
},
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugToggleDebugCheckElevationsEnabled() async {
try {
final vmservice.Response response =
await _vmService?.callServiceExtension(
'ext.flutter.debugCheckElevationsEnabled',
);
await _vmService?.callServiceExtension(
'ext.flutter.debugCheckElevationsEnabled',
args: <dynamic, dynamic>{
'enabled': !(response.json['enabled'] == 'true')
},
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugTogglePerformanceOverlayOverride() async {
try {
final vmservice.Response response = await _vmService
?.callServiceExtension('ext.flutter.showPerformanceOverlay');
await _vmService?.callServiceExtension(
'ext.flutter.showPerformanceOverlay',
args: <dynamic, dynamic>{
'enabled': !(response.json['enabled'] == 'true')
},
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugToggleWidgetInspector() async {
try {
final vmservice.Response response = await _vmService
?.callServiceExtension('ext.flutter.debugToggleWidgetInspector');
await _vmService?.callServiceExtension(
'ext.flutter.debugToggleWidgetInspector',
args: <dynamic, dynamic>{
'enabled': !(response.json['enabled'] == 'true')
},
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugToggleProfileWidgetBuilds() async {
try {
final vmservice.Response response = await _vmService
?.callServiceExtension('ext.flutter.profileWidgetBuilds');
await _vmService?.callServiceExtension(
'ext.flutter.profileWidgetBuilds',
args: <dynamic, dynamic>{
'enabled': !(response.json['enabled'] == 'true')
},
);
} on vmservice.RPCError {
return;
}
}
}
class _ExperimentalResidentWebRunner extends ResidentWebRunner {
_ExperimentalResidentWebRunner(
FlutterDevice device, {
String target,
@required FlutterProject flutterProject,
@required bool ipv6,
@required DebuggingOptions debuggingOptions,
bool stayResident = true,
}) : super(
device,
flutterProject: flutterProject,
target: target ?? fs.path.join('lib', 'main.dart'),
debuggingOptions: debuggingOptions,
ipv6: ipv6,
stayResident: stayResident,
);
@override
Future<int> run({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
String route,
}) async {
firstBuildTime = DateTime.now();
final ApplicationPackage package = await ApplicationPackageFactory.instance.getPackageForPlatform(
TargetPlatform.web_javascript,
applicationBinary: null,
);
if (package == null) {
printError('This application is not configured to build on the web.');
printError('No application found for TargetPlatform.web_javascript.');
printError('To add web support to a project, run `flutter create .`.');
return 1;
}
@ -383,200 +174,7 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner {
return 1;
}
final String modeName = debuggingOptions.buildInfo.friendlyModeName;
printStatus('Launching ${getDisplayPath(target)} on ${device.device.name} in $modeName mode...');
final String effectiveHostname = debuggingOptions.hostname ?? 'localhost';
final int hostPort = debuggingOptions.port == null
? await os.findFreePort()
: int.tryParse(debuggingOptions.port);
device.devFS = WebDevFS(
effectiveHostname,
hostPort,
packagesFilePath,
);
await device.devFS.create();
await _updateDevFS(fullRestart: true);
device.generator.accept();
await device.device.startApp(
package,
mainPath: target,
debuggingOptions: debuggingOptions,
platformArgs: <String, Object>{
'uri': 'http://$effectiveHostname:$hostPort',
},
);
return attach(
connectionInfoCompleter: connectionInfoCompleter,
appStartedCompleter: appStartedCompleter,
);
}
@override
Future<OperationResult> restart({
bool fullRestart = false,
bool pauseAfterRestart = false,
String reason,
bool benchmarkMode = false,
}) async {
final Stopwatch timer = Stopwatch()..start();
final Status status = logger.startProgress(
'Performing hot restart...',
timeout: supportsServiceProtocol
? timeoutConfiguration.fastOperation
: timeoutConfiguration.slowOperation,
progressId: 'hot.restart',
);
final UpdateFSReport report = await _updateDevFS(fullRestart: fullRestart);
if (report.success) {
device.generator.accept();
} else {
await device.generator.reject();
}
final String modules = report.invalidatedModules
.map((String module) => '"$module"')
.join(',');
try {
if (fullRestart) {
await _wipConnection.sendCommand('Page.reload');
} else {
await _wipConnection.debugger
.sendCommand('Runtime.evaluate', params: <String, Object>{
'expression': 'window.\$hotReloadHook([$modules])',
'awaitPromise': true,
'returnByValue': true,
});
}
} on WipError catch (err) {
printError(err.toString());
return OperationResult(1, err.toString());
} finally {
status.stop();
}
final String verb = fullRestart ? 'Restarted' : 'Reloaded';
printStatus('$verb application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
if (!fullRestart) {
flutterUsage.sendTiming('hot', 'web-incremental-restart', timer.elapsed);
}
HotEvent(
'restart',
targetPlatform: getNameForTargetPlatform(TargetPlatform.web_javascript),
sdkName: await device.device.sdkNameAndVersion,
emulator: false,
fullRestart: true,
reason: reason,
).send();
return OperationResult.ok;
}
Future<UpdateFSReport> _updateDevFS({bool fullRestart = false}) async {
final bool isFirstUpload = !assetBundle.wasBuiltOnce();
final bool rebuildBundle = assetBundle.needsBuild();
if (rebuildBundle) {
printTrace('Updating assets');
final int result = await assetBundle.build();
if (result != 0) {
return UpdateFSReport(success: false);
}
}
final List<Uri> invalidatedFiles =
await ProjectFileInvalidator.findInvalidated(
lastCompiled: device.devFS.lastCompiled,
urisToMonitor: device.devFS.sources,
packagesPath: packagesFilePath,
);
final Status devFSStatus = logger.startProgress(
'Syncing files to device ${device.device.name}...',
timeout: timeoutConfiguration.fastOperation,
);
final UpdateFSReport report = await device.devFS.update(
mainPath: mainPath,
target: target,
bundle: assetBundle,
firstBuildTime: firstBuildTime,
bundleFirstUpload: isFirstUpload,
generator: device.generator,
fullRestart: fullRestart,
dillOutputPath: dillOutputPath,
projectRootPath: projectRootPath,
pathToReload: getReloadPath(fullRestart: fullRestart),
invalidatedFiles: invalidatedFiles,
trackWidgetCreation: true,
);
devFSStatus.stop();
printTrace('Synced ${getSizeAsMB(report.syncedBytes)}.');
return report;
}
@override
Future<int> attach({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
}) async {
final Chrome chrome = await ChromeLauncher.connectedInstance;
final ChromeTab chromeTab =
await chrome.chromeConnection.getTab((ChromeTab chromeTab) {
return chromeTab.url.contains(debuggingOptions.hostname);
});
_wipConnection = await chromeTab.connect();
appStartedCompleter?.complete();
connectionInfoCompleter?.complete();
if (stayResident) {
await waitForAppToFinish();
} else {
await stopEchoingDeviceLog();
await exitApp();
}
await cleanupAtFinish();
return 0;
}
}
class _DwdsResidentWebRunner extends ResidentWebRunner {
_DwdsResidentWebRunner(
FlutterDevice device, {
String target,
@required FlutterProject flutterProject,
@required bool ipv6,
@required DebuggingOptions debuggingOptions,
bool stayResident = true,
}) : super(
device,
flutterProject: flutterProject,
target: target ?? fs.path.join('lib', 'main.dart'),
debuggingOptions: debuggingOptions,
ipv6: ipv6,
stayResident: stayResident,
);
@override
Future<int> run({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
String route,
}) async {
firstBuildTime = DateTime.now();
final ApplicationPackage package = await ApplicationPackageFactory.instance.getPackageForPlatform(
TargetPlatform.web_javascript,
applicationBinary: null,
);
if (package == null) {
printError('This application is not configured to build on the web.');
printError('To add web support to a project, run `flutter create .`.');
return 1;
}
if (!fs.isFileSync(mainPath)) {
String message = 'Tried to run $mainPath, but that file does not exist.';
if (target == null) {
message +=
'\nConsider using the -t option to specify the Dart file to start.';
}
printError(message);
return 1;
}
final String modeName = debuggingOptions.buildInfo.friendlyModeName;
printStatus(
'Launching ${getDisplayPath(target)} on ${device.device.name} in $modeName mode...');
printStatus('Launching ${getDisplayPath(target)} on ${device.name} in $modeName mode...');
Status buildStatus;
bool statusActive = false;
try {
@ -606,8 +204,7 @@ class _DwdsResidentWebRunner extends ResidentWebRunner {
);
statusActive = true;
}
await device.device.startApp(
package,
await device.startApp(package,
mainPath: target,
debuggingOptions: debuggingOptions,
platformArgs: <String, Object>{
@ -632,38 +229,42 @@ class _DwdsResidentWebRunner extends ResidentWebRunner {
} on VersionSkew {
// Thrown if an older build daemon is already running.
throwToolExit(
'Another build daemon is already running with an older version.\n'
'Try exiting other Flutter processes in this project and try again.');
'Another build daemon is already running with an older version.\n'
'Try exiting other Flutter processes in this project and try again.'
);
} on OptionsSkew {
// Thrown if a build daemon is already running with different configuration.
throwToolExit(
'Another build daemon is already running with different configuration.\n'
'Exit other Flutter processes running in this project and try again.');
'Another build daemon is already running with different configuration.\n'
'Try exiting other Flutter processes in this project and try again.'
);
} on WebSocketException {
throwToolExit('Failed to connect to WebSocket.');
} on BuildException {
throwToolExit('Failed to build application for the Web.');
} on ChromeDebugException catch (err, stackTrace) {
throwToolExit(
'Failed to establish connection with Chrome. Try running the application again.\n'
'If this problem persists, please file an issue with the details below:\n$err\n$stackTrace');
'Failed to establish connection with Chrome. Try running the application again.\n'
'If this problem persists, please file an issue with the details below:\n$err\n$stackTrace');
} on AppConnectionException {
throwToolExit(
'Failed to establish connection with the application instance in Chrome.\n'
'This can happen if the websocket connection used by the web tooling is '
'unabled to correctly establish a connection, for example due to a firewall.');
} on MissingPortFile {
'Failed to establish connection with the application instance in Chrome.\n'
'This can happen if the websocket connection used by the web tooling is '
'unabled to correctly establish a connection, for example due to a firewall.'
);
} on MissingPortFile {
throwToolExit(
'Failed to connect to build daemon.\nThe daemon either failed to '
'start or was killed by another process.');
'Failed to connect to build daemon.\nThe daemon either failed to '
'start or was killed by another process.');
} on SocketException catch (err) {
throwToolExit(err.toString());
} on StateError catch (err) {
final String message = err.toString();
if (message.contains('Unable to start build daemon')) {
throwToolExit('Failed to start build daemon. The process might have '
'exited unexpectedly during startup. Try running the application '
'again.');
throwToolExit(
'Failed to start build daemon. The process might have '
'exited unexpectedly during startup. Try running the application '
'again.');
}
rethrow;
} finally {
@ -674,6 +275,61 @@ class _DwdsResidentWebRunner extends ResidentWebRunner {
return 1;
}
@override
Future<int> attach({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
}) async {
Uri websocketUri;
if (supportsServiceProtocol) {
// Cleanup old subscriptions. These will throw if there isn't anything
// listening, which is fine because that is what we want to ensure.
try {
await _vmService.streamCancel('Stdout');
} on vmservice.RPCError {
// Ignore this specific error.
}
try {
await _vmService.streamListen('Stdout');
} on vmservice.RPCError {
// Ignore this specific error.
}
_stdOutSub = _vmService.onStdoutEvent.listen((vmservice.Event log) {
final String message = utf8.decode(base64.decode(log.bytes)).trim();
printStatus(message);
});
unawaited(_vmService.registerService('reloadSources', 'FlutterTools'));
websocketUri = Uri.parse(_connectionResult.debugConnection.uri);
// Always run main after connecting because start paused doesn't work yet.
if (!debuggingOptions.startPaused || !supportsServiceProtocol) {
_connectionResult.appConnection.runMain();
} else {
StreamSubscription<void> resumeSub;
resumeSub = _connectionResult.debugConnection.vmService.onDebugEvent.listen((vmservice.Event event) {
if (event.type == vmservice.EventKind.kResume) {
_connectionResult.appConnection.runMain();
resumeSub.cancel();
}
});
}
}
if (websocketUri != null) {
printStatus('Debug service listening on $websocketUri');
}
connectionInfoCompleter?.complete(
DebugConnectionInfo(wsUri: websocketUri)
);
if (stayResident) {
await waitForAppToFinish();
} else {
await stopEchoingDeviceLog();
await exitApp();
}
await cleanupAtFinish();
return 0;
}
@override
Future<OperationResult> restart({
bool fullRestart = false,
@ -700,11 +356,10 @@ class _DwdsResidentWebRunner extends ResidentWebRunner {
flutterUsage.sendTiming('hot', 'web-recompile', recompileDuration);
try {
final vmservice.Response reloadResponse = fullRestart
? await _vmService.callServiceExtension('fullReload')
: await _vmService.callServiceExtension('hotRestart');
? await _vmService.callServiceExtension('fullReload')
: await _vmService.callServiceExtension('hotRestart');
final String verb = fullRestart ? 'Restarted' : 'Reloaded';
printStatus(
'$verb application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
printStatus('$verb application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
// Send timing analytics for full restart and for refresh.
final bool wasSuccessful = reloadResponse.type == 'Success';
@ -720,10 +375,9 @@ class _DwdsResidentWebRunner extends ResidentWebRunner {
return OperationResult(1, 'Page requires refresh.');
} finally {
status.stop();
HotEvent(
'restart',
HotEvent('restart',
targetPlatform: getNameForTargetPlatform(TargetPlatform.web_javascript),
sdkName: await device.device.sdkNameAndVersion,
sdkName: await device.sdkNameAndVersion,
emulator: false,
fullRestart: true,
reason: reason,
@ -751,58 +405,159 @@ class _DwdsResidentWebRunner extends ResidentWebRunner {
}
@override
Future<int> attach({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
}) async {
Uri websocketUri;
if (supportsServiceProtocol) {
// Cleanup old subscriptions. These will throw if there isn't anything
// listening, which is fine because that is what we want to ensure.
try {
await _vmService.streamCancel('Stdout');
} on vmservice.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're not already subscribed.
}
try {
await _vmService.streamListen('Stdout');
} on vmservice.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're not already subscribed.
}
_stdOutSub = _vmService.onStdoutEvent.listen((vmservice.Event log) {
final String message = utf8.decode(base64.decode(log.bytes)).trim();
printStatus(message);
});
unawaited(_vmService.registerService('reloadSources', 'FlutterTools'));
websocketUri = Uri.parse(_connectionResult.debugConnection.uri);
// Always run main after connecting because start paused doesn't work yet.
if (!debuggingOptions.startPaused || !supportsServiceProtocol) {
_connectionResult.appConnection.runMain();
} else {
StreamSubscription<void> resumeSub;
resumeSub = _connectionResult.debugConnection.vmService.onDebugEvent
.listen((vmservice.Event event) {
if (event.type == vmservice.EventKind.kResume) {
_connectionResult.appConnection.runMain();
resumeSub.cancel();
}
});
}
Future<void> debugDumpApp() async {
try {
await _vmService.callServiceExtension(
'ext.flutter.debugDumpApp',
);
} on vmservice.RPCError {
return;
}
if (websocketUri != null) {
printStatus('Debug service listening on $websocketUri');
}
connectionInfoCompleter?.complete(DebugConnectionInfo(wsUri: websocketUri));
}
if (stayResident) {
await waitForAppToFinish();
} else {
await stopEchoingDeviceLog();
await exitApp();
@override
Future<void> debugDumpRenderTree() async {
try {
await _vmService.callServiceExtension(
'ext.flutter.debugDumpRenderTree',
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugDumpLayerTree() async {
try {
await _vmService.callServiceExtension(
'ext.flutter.debugDumpLayerTree',
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugDumpSemanticsTreeInTraversalOrder() async {
try {
await _vmService.callServiceExtension(
'ext.flutter.debugDumpSemanticsTreeInTraversalOrder');
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugTogglePlatform() async {
try {
final vmservice.Response response = await _vmService.callServiceExtension(
'ext.flutter.platformOverride');
final String currentPlatform = response.json['value'];
String nextPlatform;
switch (currentPlatform) {
case 'android':
nextPlatform = 'iOS';
break;
case 'iOS':
nextPlatform = 'android';
break;
}
if (nextPlatform == null) {
return;
}
await _vmService.callServiceExtension(
'ext.flutter.platformOverride', args: <String, Object>{
'value': nextPlatform,
});
printStatus('Switched operating system to $nextPlatform');
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
try {
await _vmService.callServiceExtension(
'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder');
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugToggleDebugPaintSizeEnabled() async {
try {
final vmservice.Response response = await _vmService.callServiceExtension(
'ext.flutter.debugPaint',
);
await _vmService.callServiceExtension(
'ext.flutter.debugPaint',
args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugToggleDebugCheckElevationsEnabled() async {
try {
final vmservice.Response response = await _vmService.callServiceExtension(
'ext.flutter.debugCheckElevationsEnabled',
);
await _vmService.callServiceExtension(
'ext.flutter.debugCheckElevationsEnabled',
args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugTogglePerformanceOverlayOverride() async {
try {
final vmservice.Response response = await _vmService.callServiceExtension(
'ext.flutter.showPerformanceOverlay'
);
await _vmService.callServiceExtension(
'ext.flutter.showPerformanceOverlay',
args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugToggleWidgetInspector() async {
try {
final vmservice.Response response = await _vmService.callServiceExtension(
'ext.flutter.debugToggleWidgetInspector'
);
await _vmService.callServiceExtension(
'ext.flutter.debugToggleWidgetInspector',
args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
);
} on vmservice.RPCError {
return;
}
}
@override
Future<void> debugToggleProfileWidgetBuilds() async {
try {
final vmservice.Response response = await _vmService.callServiceExtension(
'ext.flutter.profileWidgetBuilds'
);
await _vmService.callServiceExtension(
'ext.flutter.profileWidgetBuilds',
args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
);
} on vmservice.RPCError {
return;
}
await cleanupAtFinish();
return 0;
}
}

View file

@ -430,7 +430,7 @@ class AppDomain extends Domain {
if (await device.targetPlatform == TargetPlatform.web_javascript) {
runner = webRunnerFactory.createWebRunner(
flutterDevice,
device,
flutterProject: flutterProject,
target: target,
debuggingOptions: options,

View file

@ -453,7 +453,7 @@ class RunCommand extends RunCommandBase {
);
} else if (webMode) {
runner = webRunnerFactory.createWebRunner(
flutterDevices.single,
devices.single,
target: targetFile,
flutterProject: flutterProject,
ipv6: ipv6,

View file

@ -201,6 +201,7 @@ class PackageUriMapper {
PackageUriMapper(String scriptPath, String packagesPath, String fileSystemScheme, List<String> fileSystemRoots) {
final Map<String, Uri> packageMap = PackageMap(fs.path.absolute(packagesPath)).map;
final String scriptUri = Uri.file(scriptPath, windows: platform.isWindows).toString();
for (String packageName in packageMap.keys) {
final String prefix = packageMap[packageName].toString();
// Only perform a multi-root mapping if there are multiple roots.

View file

@ -349,19 +349,12 @@ class UpdateFSReport {
int get invalidatedSourcesCount => _invalidatedSourcesCount;
int get syncedBytes => _syncedBytes;
/// JavaScript modules produced by the incremental compiler in `dartdevc`
/// mode.
///
/// Only used for JavaScript compilation.
List<String> invalidatedModules;
void incorporateResults(UpdateFSReport report) {
if (!report._success) {
_success = false;
}
_invalidatedSourcesCount += report._invalidatedSourcesCount;
_syncedBytes += report._syncedBytes;
invalidatedModules ??= report.invalidatedModules;
}
bool _success;

View file

@ -151,15 +151,10 @@ const Feature flutterAndroidEmbeddingV2Feature = Feature(
const Feature flutterWebIncrementalCompiler = Feature(
name: 'Enable the incremental compiler for web builds',
configSetting: 'enable-web-incremental-compiler',
environmentOverride: 'WEB_INCREMENTAL_COMPILER',
master: FeatureChannelSetting(
available: true,
enabledByDefault: false,
),
dev: FeatureChannelSetting(
available: true,
enabledByDefault: false,
),
);
/// A [Feature] is a process for conditionally enabling tool features.

View file

@ -75,25 +75,21 @@ define("main_module", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) {
dart_sdk.dart.setStartAsyncSynchronously(true);
dart_sdk._isolate_helper.startRootIsolate(() => {}, []);
dart_sdk._debugger.registerDevtoolsFormatter();
let voidToNull = () => (voidToNull = dart_sdk.dart.constFn(dart_sdk.dart.fnType(dart_sdk.core.Null, [dart_sdk.dart.void])))();
dart_sdk.ui.webOnlyInitializePlatform();
// Attach the main entrypoint and hot reload functionality to the window.
window.\$mainEntrypoint = app.main.main;
if (window.\$hotReload == null) {
window.\$hotReload = function(cb) {
dart_sdk.developer.invokeExtension("ext.flutter.disassemble", "{}").then((_) => {
dart_sdk.dart.hotRestart();
dart_sdk.ui.webOnlyInitializePlatform().then(dart_sdk.core.Null, dart_sdk.dart.fn(_ => {
window.\$mainEntrypoint();
window.requestAnimationFrame(cb);
}, voidToNull()));
});
dart_sdk.developer.invokeExtension("ext.flutter.disassemble", "{}");
dart_sdk.dart.hotRestart();
window.\$mainEntrypoint();
if (cb != null) {
cb();
}
}
}
dart_sdk.ui.webOnlyInitializePlatform().then(dart_sdk.core.Null, dart_sdk.dart.fn(_ => {
app.main.main();
}, voidToNull()));
app.main.main();
});
// Require JS configuration.

View file

@ -71,11 +71,6 @@ void resetChromeForTesting() {
ChromeLauncher._currentCompleter = Completer<Chrome>();
}
@visibleForTesting
void launchChromeInstance(Chrome chrome) {
ChromeLauncher._currentCompleter.complete(chrome);
}
/// Responsible for launching chrome with devtools configured.
class ChromeLauncher {
const ChromeLauncher();

View file

@ -7,18 +7,12 @@ import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'package:mime/mime.dart' as mime;
import '../artifacts.dart';
import '../asset.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../build_info.dart';
import '../bundle.dart';
import '../compile.dart';
import '../convert.dart';
import '../devfs.dart';
import '../globals.dart';
import 'bootstrap.dart';
/// A web server which handles serving JavaScript and assets.
///
@ -55,10 +49,7 @@ class WebAssetServer {
}
final HttpServer _httpServer;
// If holding these in memory is too much overhead, this can be switched to a
// RandomAccessFile and read on demand.
final Map<String, Uint8List> _files = <String, Uint8List>{};
final Map<String, Uint8List> _sourcemaps = <String, Uint8List>{};
// handle requests for JavaScript source, dart sources maps, or asset files.
Future<void> _handleRequest(HttpRequest request) async {
@ -80,7 +71,8 @@ class WebAssetServer {
}
// If this is a JavaScript file, it must be in the in-memory cache.
// Attempt to look up the file by URI.
// Attempt to look up the file by URI, returning a 404 if it is not
// found.
if (_files.containsKey(request.uri.path)) {
final List<int> bytes = _files[request.uri.path];
response.headers
@ -90,18 +82,6 @@ class WebAssetServer {
await response.close();
return;
}
// If this is a sourcemap file, then it might be in the in-memory cache.
// Attempt to lookup the file by URI.
if (_sourcemaps.containsKey(request.uri.path)) {
final List<int> bytes = _sourcemaps[request.uri.path];
response.headers
..add('Content-Length', bytes.length)
..add('Content-Type', 'application/json');
response.add(bytes);
await response.close();
return;
}
// If this is a dart file, it must be on the local file system and is
// likely coming from a source map request. Attempt to look in the
// local filesystem for it, and return a 404 if it is not found. The tool
@ -115,18 +95,6 @@ class WebAssetServer {
file = fs.file(fs.path.join(getAssetBuildDirectory(), fs.path.relative(assetPath)));
}
// If it isn't a project source or an asset, it must be a dart SDK source.
// or a flutter web SDK source.
if (!file.existsSync()) {
final Directory dartSdkParent = fs.directory(artifacts.getArtifactPath(Artifact.engineDartSdkPath)).parent;
file = fs.file(fs.path.joinAll(<String>[dartSdkParent.path, ...request.uri.pathSegments]));
}
if (!file.existsSync()) {
final String flutterWebSdk = artifacts.getArtifactPath(Artifact.flutterWebSdk);
file = fs.file(fs.path.joinAll(<String>[flutterWebSdk, ...request.uri.pathSegments]));
}
if (!file.existsSync()) {
response.statusCode = HttpStatus.notFound;
await response.close();
@ -163,186 +131,30 @@ class WebAssetServer {
/// Update the in-memory asset server with the provided source and manifest files.
///
/// Returns a list of updated modules.
List<String> write(File codeFile, File manifestFile, File sourcemapFile) {
List<String> write(File sourceFile, File manifestFile) {
final List<String> modules = <String>[];
final Uint8List codeBytes = codeFile.readAsBytesSync();
final Uint8List sourcemapBytes = sourcemapFile.readAsBytesSync();
final Uint8List bytes = sourceFile.readAsBytesSync();
final Map<String, Object> manifest = json.decode(manifestFile.readAsStringSync());
for (String filePath in manifest.keys) {
if (filePath == null) {
printTrace('Invalid manfiest file: $filePath');
continue;
}
final Map<String, Object> offsets = manifest[filePath];
final List<Object> codeOffsets = offsets['code'];
final List<Object> sourcemapOffsets = offsets['sourcemap'];
if (codeOffsets.length != 2 || sourcemapOffsets.length != 2) {
final List<Object> offsets = manifest[filePath];
if (offsets.length != 2) {
printTrace('Invalid manifest byte offsets: $offsets');
continue;
}
final int codeStart = codeOffsets[0];
final int codeEnd = codeOffsets[1];
if (codeStart < 0 || codeEnd > codeBytes.lengthInBytes) {
printTrace('Invalid byte index: [$codeStart, $codeEnd]');
final int start = offsets[0];
final int end = offsets[1];
if (start < 0 || end > bytes.lengthInBytes) {
printTrace('Invalid byte index: [$start, $end]');
continue;
}
final Uint8List byteView = Uint8List.view(
codeBytes.buffer,
codeStart,
codeEnd - codeStart,
);
final Uint8List byteView = Uint8List.view(bytes.buffer, start, end - start);
_files[filePath] = byteView;
final int sourcemapStart = sourcemapOffsets[0];
final int sourcemapEnd = sourcemapOffsets[1];
if (sourcemapStart < 0 || sourcemapEnd > sourcemapBytes.lengthInBytes) {
printTrace('Invalid byte index: [$sourcemapStart, $sourcemapEnd]');
continue;
}
final Uint8List sourcemapView = Uint8List.view(
sourcemapBytes.buffer,
sourcemapStart,
sourcemapEnd - sourcemapStart ,
);
_sourcemaps['$filePath.map'] = sourcemapView;
modules.add(filePath);
}
return modules;
}
}
class WebDevFS implements DevFS {
WebDevFS(this.hostname, this.port, this._packagesFilePath);
final String hostname;
final int port;
final String _packagesFilePath;
WebAssetServer _webAssetServer;
@override
List<Uri> sources = <Uri>[];
@override
DateTime lastCompiled;
// We do not evict assets on the web.
@override
Set<String> get assetPathsToEvict => const <String>{};
@override
Uri get baseUri => null;
@override
Future<Uri> create() async {
_webAssetServer = await WebAssetServer.start(hostname, port);
return Uri.base;
}
@override
Future<void> destroy() async {
await _webAssetServer.dispose();
}
@override
Uri deviceUriToHostUri(Uri deviceUri) {
return deviceUri;
}
@override
String get fsName => 'web_asset';
@override
Directory get rootDirectory => null;
@override
Future<UpdateFSReport> update({
String mainPath,
String target,
AssetBundle bundle,
DateTime firstBuildTime,
bool bundleFirstUpload = false,
@required ResidentCompiler generator,
String dillOutputPath,
@required bool trackWidgetCreation,
bool fullRestart = false,
String projectRootPath,
String pathToReload,
List<Uri> invalidatedFiles,
}) async {
assert(trackWidgetCreation != null);
assert(generator != null);
if (bundleFirstUpload) {
final File requireJS = fs.file(fs.path.join(
artifacts.getArtifactPath(Artifact.engineDartSdkPath),
'lib',
'dev_compiler',
'kernel',
'amd',
'require.js',
));
final File dartSdk = fs.file(fs.path.join(
artifacts.getArtifactPath(Artifact.flutterWebSdk),
'kernel',
'amd',
'dart_sdk.js',
));
final File dartSdkSourcemap = fs.file(fs.path.join(
artifacts.getArtifactPath(Artifact.flutterWebSdk),
'kernel',
'amd',
'dart_sdk.js.map',
));
final File stackTraceMapper = fs.file(fs.path.join(
artifacts.getArtifactPath(Artifact.engineDartSdkPath),
'lib',
'dev_compiler',
'web',
'dart_stack_trace_mapper.js',
));
_webAssetServer.writeFile('/main.dart.js', generateBootstrapScript(
requireUrl: requireJS.path,
mapperUrl: stackTraceMapper.path,
entrypoint: '$mainPath.js',
));
_webAssetServer.writeFile('/main_module.js', generateMainModule(
entrypoint: '$mainPath.js',
));
_webAssetServer.writeFile('/dart_sdk.js', dartSdk.readAsStringSync());
_webAssetServer.writeFile('/dart_sdk.js.map', dartSdkSourcemap.readAsStringSync());
}
final DateTime candidateCompileTime = DateTime.now();
if (fullRestart) {
generator.reset();
}
final CompilerOutput compilerOutput = await generator.recompile(
mainPath,
invalidatedFiles,
outputPath: dillOutputPath ?? getDefaultApplicationKernelPath(trackWidgetCreation: trackWidgetCreation),
packagesFilePath : _packagesFilePath,
);
if (compilerOutput == null || compilerOutput.errorCount > 0) {
return UpdateFSReport(success: false);
}
// Only update the last compiled time if we successfully compiled.
lastCompiled = candidateCompileTime;
// list of sources that needs to be monitored are in [compilerOutput.sources]
sources = compilerOutput.sources;
File codeFile;
File manifestFile;
File sourcemapFile;
List<String> modules;
try {
codeFile = fs.file('${compilerOutput.outputFilename}.sources');
manifestFile = fs.file('${compilerOutput.outputFilename}.json');
sourcemapFile = fs.file('${compilerOutput.outputFilename}.map');
modules = _webAssetServer.write(codeFile, manifestFile, sourcemapFile);
} on FileSystemException catch (err) {
throwToolExit('Failed to load recompiled sources:\n$err');
}
return UpdateFSReport(success: true, syncedBytes: codeFile.lengthSync(),
invalidatedSourcesCount: invalidatedFiles.length)
..invalidatedModules = modules;
}
}

View file

@ -17,7 +17,7 @@ abstract class WebRunnerFactory {
/// Create a [ResidentRunner] for the web.
ResidentRunner createWebRunner(
FlutterDevice device, {
Device device, {
String target,
@required bool stayResident,
@required FlutterProject flutterProject,

View file

@ -63,12 +63,11 @@ void main() {
test('Refuses to build using runner when missing index.html', () => testbed.run(() async {
fs.file(fs.path.join('web', 'index.html')).deleteSync();
final ResidentWebRunner runner = DwdsWebRunnerFactory().createWebRunner(
final ResidentWebRunner runner = ResidentWebRunner(
null,
flutterProject: FlutterProject.current(),
ipv6: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
stayResident: true,
);
expect(await runner.run(), 1);
}));

View file

@ -26,21 +26,17 @@ void main() {
Testbed testbed;
MockFlutterWebFs mockWebFs;
ResidentWebRunner residentWebRunner;
MockFlutterDevice mockFlutterDevice;
setUp(() {
mockWebFs = MockFlutterWebFs();
final MockWebDevice mockWebDevice = MockWebDevice();
mockFlutterDevice = MockFlutterDevice();
when(mockFlutterDevice.device).thenReturn(mockWebDevice);
testbed = Testbed(
setup: () {
residentWebRunner = residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
residentWebRunner = ResidentWebRunner(
mockWebDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
ipv6: true,
stayResident: true,
);
},
overrides: <Type, Generator>{
@ -122,4 +118,4 @@ class MockFlutterWebFs extends Mock implements WebFs {}
class MockDebugConnection extends Mock implements DebugConnection {}
class MockVmService extends Mock implements VmService {}
class MockStatus extends Mock implements Status {}
class MockFlutterDevice extends Mock implements FlutterDevice {}

View file

@ -12,22 +12,17 @@ import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/build_runner/resident_web_runner.dart';
import 'package:flutter_tools/src/build_runner/web_fs.dart';
import 'package:flutter_tools/src/web/chrome.dart';
import 'package:flutter_tools/src/web/web_device.dart';
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
import 'package:vm_service/vm_service.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
import '../src/common.dart';
import '../src/testbed.dart';
@ -38,41 +33,22 @@ void main() {
ResidentWebRunner residentWebRunner;
MockDebugConnection mockDebugConnection;
MockVmService mockVmService;
MockChromeDevice mockChromeDevice;
MockWebDevice mockWebDevice;
MockAppConnection mockAppConnection;
MockFlutterDevice mockFlutterDevice;
MockWebDevFS mockWebDevFS;
MockResidentCompiler mockResidentCompiler;
MockChrome mockChrome;
MockChromeConnection mockChromeConnection;
MockChromeTab mockChromeTab;
MockWipConnection mockWipConnection;
MockWipDebugger mockWipDebugger;
setUp(() {
resetChromeForTesting();
mockWebFs = MockFlutterWebFs();
mockDebugConnection = MockDebugConnection();
mockVmService = MockVmService();
mockChromeDevice = MockChromeDevice();
mockWebDevice = MockWebDevice();
mockAppConnection = MockAppConnection();
mockFlutterDevice = MockFlutterDevice();
mockWebDevFS = MockWebDevFS();
mockResidentCompiler = MockResidentCompiler();
mockChrome = MockChrome();
mockChromeConnection = MockChromeConnection();
mockChromeTab = MockChromeTab();
mockWipConnection = MockWipConnection();
mockWipDebugger = MockWipDebugger();
when(mockFlutterDevice.device).thenReturn(mockChromeDevice);
testbed = Testbed(
setup: () {
residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
residentWebRunner = ResidentWebRunner(
mockWebDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
stayResident: true,
);
},
overrides: <Type, Generator>{
@ -112,44 +88,26 @@ void main() {
return const Stream<Event>.empty();
});
when(mockDebugConnection.uri).thenReturn('ws://127.0.0.1/abcd/');
when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS);
when(mockWebDevFS.sources).thenReturn(<Uri>[]);
when(mockFlutterDevice.generator).thenReturn(mockResidentCompiler);
when(mockChrome.chromeConnection).thenReturn(mockChromeConnection);
when(mockChromeConnection.getTab(any)).thenAnswer((Invocation invocation) async {
return mockChromeTab;
});
when(mockChromeTab.connect()).thenAnswer((Invocation invocation) async {
return mockWipConnection;
});
when(mockWipConnection.debugger).thenReturn(mockWipDebugger);
}
test('runner with web server device does not support debugging', () => testbed.run(() {
when(mockFlutterDevice.device).thenReturn(WebServerDevice());
final ResidentRunner profileResidentWebRunner = residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
final ResidentRunner profileResidentWebRunner = ResidentWebRunner(
WebServerDevice(),
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
stayResident: true,
);
expect(profileResidentWebRunner.debuggingEnabled, false);
when(mockFlutterDevice.device).thenReturn(MockChromeDevice());
expect(residentWebRunner.debuggingEnabled, true);
}));
test('profile does not supportsServiceProtocol', () => testbed.run(() {
when(mockFlutterDevice.device).thenReturn(mockChromeDevice);
final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
final ResidentRunner profileResidentWebRunner = ResidentWebRunner(
MockWebDevice(),
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile),
ipv6: true,
stayResident: true,
);
expect(profileResidentWebRunner.supportsServiceProtocol, false);
@ -161,7 +119,7 @@ void main() {
final BufferLogger bufferLogger = logger;
expect(await residentWebRunner.run(), 1);
expect(bufferLogger.errorText, contains('This application is not configured to build on the web'));
expect(bufferLogger.errorText, contains('No application found for TargetPlatform.web_javascript'));
}));
test('Exits on run if target file does not exist', () => testbed.run(() async {
@ -198,8 +156,8 @@ void main() {
test('Can successfully run and disconnect with --no-resident', () => testbed.run(() async {
_setupMocks();
residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
residentWebRunner = ResidentWebRunner(
mockWebDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
@ -234,12 +192,11 @@ void main() {
}));
test('Does not run main with --start-paused', () => testbed.run(() async {
residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
residentWebRunner = ResidentWebRunner(
mockWebDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true),
ipv6: true,
stayResident: true,
);
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
@ -287,94 +244,6 @@ void main() {
Usage: () => MockFlutterUsage(),
}));
test('Can hot reload after attaching - experimental', () => testbed.run(() async {
_setupMocks();
launchChromeInstance(mockChrome);
when(mockWebDevFS.update(
mainPath: anyNamed('mainPath'),
target: anyNamed('target'),
bundle: anyNamed('bundle'),
firstBuildTime: anyNamed('firstBuildTime'),
bundleFirstUpload: anyNamed('bundleFirstUpload'),
generator: anyNamed('generator'),
fullRestart: anyNamed('fullRestart'),
dillOutputPath: anyNamed('dillOutputPath'),
trackWidgetCreation: anyNamed('trackWidgetCreation'),
projectRootPath: anyNamed('projectRootPath'),
pathToReload: anyNamed('pathToReload'),
invalidatedFiles: anyNamed('invalidatedFiles'),
)).thenAnswer((Invocation invocation) async {
return UpdateFSReport(success: true)
..invalidatedModules = <String>['example'];
});
final BufferLogger bufferLogger = logger;
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
final OperationResult result = await residentWebRunner.restart(fullRestart: false);
expect(bufferLogger.statusText, contains('Reloaded application in'));
expect(result.code, 0);
verify(mockResidentCompiler.accept()).called(2);
// ensure that analytics are sent.
verify(Usage.instance.sendEvent('hot', 'restart', parameters: <String, String>{
'cd27': 'web-javascript',
'cd28': null,
'cd29': 'false',
'cd30': 'true',
})).called(1);
verify(Usage.instance.sendTiming('hot', 'web-incremental-restart', any)).called(1);
}, overrides: <Type, Generator>{
Usage: () => MockFlutterUsage(),
FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true),
}));
test('Can hot restart after attaching - experimental', () => testbed.run(() async {
_setupMocks();
launchChromeInstance(mockChrome);
when(mockWebDevFS.update(
mainPath: anyNamed('mainPath'),
target: anyNamed('target'),
bundle: anyNamed('bundle'),
firstBuildTime: anyNamed('firstBuildTime'),
bundleFirstUpload: anyNamed('bundleFirstUpload'),
generator: anyNamed('generator'),
fullRestart: anyNamed('fullRestart'),
dillOutputPath: anyNamed('dillOutputPath'),
trackWidgetCreation: anyNamed('trackWidgetCreation'),
projectRootPath: anyNamed('projectRootPath'),
pathToReload: anyNamed('pathToReload'),
invalidatedFiles: anyNamed('invalidatedFiles'),
)).thenAnswer((Invocation invocation) async {
return UpdateFSReport(success: true)
..invalidatedModules = <String>['example'];
});
final BufferLogger bufferLogger = logger;
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
expect(bufferLogger.statusText, contains('Restarted application in'));
expect(result.code, 0);
verify(mockResidentCompiler.accept()).called(2);
// ensure that analytics are sent.
verify(Usage.instance.sendEvent('hot', 'restart', parameters: <String, String>{
'cd27': 'web-javascript',
'cd28': null,
'cd29': 'false',
'cd30': 'true',
})).called(1);
verifyNever(Usage.instance.sendTiming('hot', 'web-incremental-restart', any));
}, overrides: <Type, Generator>{
Usage: () => MockFlutterUsage(),
FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true),
}));
test('Can hot restart after attaching', () => testbed.run(() async {
_setupMocks();
final BufferLogger bufferLogger = logger;
@ -657,7 +526,7 @@ void main() {
test('cleanup of resources is safe to call multiple times', () => testbed.run(() async {
_setupMocks();
bool debugClosed = false;
when(mockChromeDevice.stopApp(any)).thenAnswer((Invocation invocation) async {
when(mockWebDevice.stopApp(any)).thenAnswer((Invocation invocation) async {
if (debugClosed) {
throw StateError('debug connection closed twice');
}
@ -695,7 +564,7 @@ void main() {
test('Prints target and device name on run', () => testbed.run(() async {
_setupMocks();
when(mockChromeDevice.name).thenReturn('Chromez');
when(mockWebDevice.name).thenReturn('Chromez');
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
@ -887,18 +756,10 @@ void main() {
}
class MockFlutterUsage extends Mock implements Usage {}
class MockChromeDevice extends Mock implements ChromeDevice {}
class MockWebDevice extends Mock implements ChromeDevice {}
class MockBuildDaemonCreator extends Mock implements BuildDaemonCreator {}
class MockFlutterWebFs extends Mock implements WebFs {}
class MockDebugConnection extends Mock implements DebugConnection {}
class MockAppConnection extends Mock implements AppConnection {}
class MockVmService extends Mock implements VmService {}
class MockStatus extends Mock implements Status {}
class MockFlutterDevice extends Mock implements FlutterDevice {}
class MockWebDevFS extends Mock implements DevFS {}
class MockResidentCompiler extends Mock implements ResidentCompiler {}
class MockChrome extends Mock implements Chrome {}
class MockChromeConnection extends Mock implements ChromeConnection {}
class MockChromeTab extends Mock implements ChromeTab {}
class MockWipConnection extends Mock implements WipConnection {}
class MockWipDebugger extends Mock implements WipDebugger {}

View file

@ -89,36 +89,28 @@ void main() {
test('Handles against malformed manifest', () => testbed.run(() async {
final File source = fs.file('source')
..writeAsStringSync('main() {}');
final File sourcemap = fs.file('sourcemap')
..writeAsStringSync('{}');
// Missing ending offset.
final File manifestMissingOffset = fs.file('manifestA')
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
'code': <int>[0],
'sourcemap': <int>[0],
}}));
final File manifestOutOfBounds = fs.file('manifest')
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
'code': <int>[0, 100],
'sourcemap': <int>[0],
}}));
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <int>[0]}));
// Non-file URI.
final File manifestNonFileScheme = fs.file('manifestA')
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <int>[0, 10]}));
expect(webAssetServer.write(source, manifestMissingOffset, sourcemap), isEmpty);
expect(webAssetServer.write(source, manifestOutOfBounds, sourcemap), isEmpty);
final File manifestOutOfBounds = fs.file('manifest')
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <int>[0, 100]}));
expect(webAssetServer.write(source, manifestMissingOffset), isEmpty);
expect(webAssetServer.write(source, manifestNonFileScheme), isEmpty);
expect(webAssetServer.write(source, manifestOutOfBounds), isEmpty);
}));
test('serves JavaScript files from in memory cache', () => testbed.run(() async {
final File source = fs.file('source')
..writeAsStringSync('main() {}');
final File sourcemap = fs.file('sourcemap')
..writeAsStringSync('{}');
final File manifest = fs.file('manifest')
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
'code': <int>[0, source.lengthSync()],
'sourcemap': <int>[0, 2],
}}));
webAssetServer.write(source, manifest, sourcemap);
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <int>[0, source.lengthSync()]}));
webAssetServer.write(source, manifest);
when(request.uri).thenReturn(Uri.parse('http://foobar/foo.js'));
requestController.add(request);
@ -144,14 +136,9 @@ void main() {
test('handles missing JavaScript files from in memory cache', () => testbed.run(() async {
final File source = fs.file('source')
..writeAsStringSync('main() {}');
final File sourcemap = fs.file('sourcemap')
..writeAsStringSync('{}');
final File manifest = fs.file('manifest')
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
'code': <int>[0, source.lengthSync()],
'sourcemap': <int>[0, 2],
}}));
webAssetServer.write(source, manifest, sourcemap);
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <int>[0, source.lengthSync()]}));
webAssetServer.write(source, manifest);
when(request.uri).thenReturn(Uri.parse('http://foobar/bar.js'));
requestController.add(request);