forked from firka/flutter
[flutter_tool] Reland: support --fast-start for Android applications (as an opt-in) (#46140)
This commit is contained in:
@@ -279,6 +279,13 @@ Future<void> _runToolTests() async {
|
||||
await selectSubshard(subshards);
|
||||
}
|
||||
|
||||
// Example apps that should not be built by _runBuildTests`
|
||||
const List<String> _excludedExampleApplications = <String>[
|
||||
// This application contains no platform code and cannot be built, except for
|
||||
// as a part of a '--fast-start' Android application.
|
||||
'splash',
|
||||
];
|
||||
|
||||
/// Verifies that AOT, APK, and IPA (if on macOS) builds the examples apps
|
||||
/// without crashing. It does not actually launch the apps. That happens later
|
||||
/// in the devicelab. This is just a smoke-test. In particular, this will verify
|
||||
@@ -290,6 +297,9 @@ Future<void> _runBuildTests() async {
|
||||
if (fileEntity is! Directory) {
|
||||
continue;
|
||||
}
|
||||
if (_excludedExampleApplications.any(fileEntity.path.endsWith)) {
|
||||
continue;
|
||||
}
|
||||
final String examplePath = fileEntity.path;
|
||||
await _flutterBuildAot(examplePath);
|
||||
await _flutterBuildApk(examplePath);
|
||||
@@ -763,6 +773,7 @@ Future<void> _runHostOnlyDeviceLabTests() async {
|
||||
if (Platform.isMacOS) () => _runDevicelabTest('flutter_create_offline_test_mac'),
|
||||
if (Platform.isLinux) () => _runDevicelabTest('flutter_create_offline_test_linux'),
|
||||
if (Platform.isWindows) () => _runDevicelabTest('flutter_create_offline_test_windows'),
|
||||
() => _runDevicelabTest('gradle_fast_start_test', environment: gradleEnvironment),
|
||||
// TODO(ianh): Fails on macOS looking for "dexdump", https://github.com/flutter/flutter/issues/42494
|
||||
if (!Platform.isMacOS) () => _runDevicelabTest('gradle_jetifier_test', environment: gradleEnvironment),
|
||||
() => _runDevicelabTest('gradle_non_android_plugin_test', environment: gradleEnvironment),
|
||||
|
||||
42
dev/devicelab/bin/tasks/gradle_fast_start_test.dart
Normal file
42
dev/devicelab/bin/tasks/gradle_fast_start_test.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2014 The Flutter 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 'dart:async';
|
||||
|
||||
import 'package:flutter_devicelab/framework/apk_utils.dart';
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
import 'package:flutter_devicelab/framework/utils.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
await task(() async {
|
||||
try {
|
||||
await runPluginProjectTest((FlutterPluginProject pluginProject) async {
|
||||
section('APK content for task assembleDebug with --fast-start');
|
||||
await pluginProject.runGradleTask('assembleDebug',
|
||||
options: <String>['-Pfast-start=true']);
|
||||
|
||||
final Iterable<String> apkFiles = await getFilesInApk(pluginProject.debugApkPath);
|
||||
|
||||
checkCollectionContains<String>(<String>[
|
||||
...debugAssets,
|
||||
...baseApkFiles,
|
||||
'lib/x86/libflutter.so',
|
||||
'lib/x86_64/libflutter.so',
|
||||
'lib/armeabi-v7a/libflutter.so',
|
||||
'lib/arm64-v8a/libflutter.so',
|
||||
], apkFiles);
|
||||
|
||||
checkCollectionDoesNotContain<String>(<String>[
|
||||
...flutterAssets,
|
||||
], apkFiles);
|
||||
});
|
||||
|
||||
return TaskResult.success(null);
|
||||
} on TaskResult catch (taskResult) {
|
||||
return taskResult;
|
||||
} catch (e) {
|
||||
return TaskResult.failure(e.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
16
examples/splash/lib/main.dart
Normal file
16
examples/splash/lib/main.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2014 The Flutter 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/material.dart';
|
||||
|
||||
void main() {
|
||||
runApp(
|
||||
const DecoratedBox(
|
||||
decoration: BoxDecoration(color: Colors.white),
|
||||
child: Center(
|
||||
child: FlutterLogo(size: 48),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
41
examples/splash/pubspec.yaml
Normal file
41
examples/splash/pubspec.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
name: splash
|
||||
|
||||
environment:
|
||||
# The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
|
||||
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
archive: 2.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
args: 1.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
async: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
image: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
path: 1.6.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
pedantic: 1.8.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
petitparser: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
quiver: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_span: 1.5.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
stream_channel: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
string_scanner: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
test_api: 0.2.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
xml: 3.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
# PUBSPEC CHECKSUM: f789
|
||||
16
examples/splash/test/splash_test.dart
Normal file
16
examples/splash/test/splash_test.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2014 The Flutter 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/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:splash/main.dart' as entrypoint;
|
||||
|
||||
void main() {
|
||||
testWidgets('Displays flutter logo', (WidgetTester tester) async {
|
||||
entrypoint.main();
|
||||
|
||||
expect(find.byType(FlutterLogo), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -472,6 +472,15 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
return false
|
||||
}
|
||||
|
||||
/// Whether to build the debug app in "fast-start" mode.
|
||||
private Boolean isFastStart() {
|
||||
if (project.hasProperty("fast-start")) {
|
||||
return project.property("fast-start").toBoolean()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
private static Boolean shouldShrinkResources(Project project) {
|
||||
if (project.hasProperty("shrink")) {
|
||||
return project.property("shrink").toBoolean()
|
||||
@@ -610,6 +619,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
localEngineSrcPath this.localEngineSrcPath
|
||||
targetPath target
|
||||
verbose isVerbose()
|
||||
fastStart isFastStart()
|
||||
fileSystemRoots fileSystemRootsValue
|
||||
fileSystemScheme fileSystemSchemeValue
|
||||
trackWidgetCreation trackWidgetCreationValue
|
||||
@@ -729,6 +739,8 @@ abstract class BaseFlutterTask extends DefaultTask {
|
||||
String localEngine
|
||||
String localEngineSrcPath
|
||||
@Input
|
||||
Boolean fastStart
|
||||
@Input
|
||||
String targetPath
|
||||
@Optional
|
||||
Boolean verbose
|
||||
@@ -769,9 +781,13 @@ abstract class BaseFlutterTask extends DefaultTask {
|
||||
// cache.
|
||||
String[] ruleNames;
|
||||
if (buildMode == "debug") {
|
||||
ruleNames = ["debug_android_application"]
|
||||
if (fastStart) {
|
||||
ruleNames = ["faststart_android_application"]
|
||||
} else {
|
||||
ruleNames = ["debug_android_application"]
|
||||
}
|
||||
} else {
|
||||
ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
|
||||
ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
|
||||
}
|
||||
project.exec {
|
||||
executable flutterExecutable.absolutePath
|
||||
@@ -788,7 +804,11 @@ abstract class BaseFlutterTask extends DefaultTask {
|
||||
args "assemble"
|
||||
args "--depfile", "${intermediateDir}/flutter_build.d"
|
||||
args "--output", "${intermediateDir}"
|
||||
args "-dTargetFile=${targetPath}"
|
||||
if (!fastStart || buildMode != "debug") {
|
||||
args "-dTargetFile=${targetPath}"
|
||||
} else {
|
||||
args "-dTargetFile=${Paths.get(flutterRoot.absolutePath, "examples", "splash", "lib", "main.dart")}"
|
||||
}
|
||||
args "-dTargetPlatform=android"
|
||||
args "-dBuildMode=${buildMode}"
|
||||
if (extraFrontEndOptions != null) {
|
||||
|
||||
@@ -527,6 +527,7 @@ class AndroidDevice extends Device {
|
||||
androidBuildInfo: AndroidBuildInfo(
|
||||
debuggingOptions.buildInfo,
|
||||
targetArchs: <AndroidArch>[androidArch],
|
||||
fastStart: debuggingOptions.fastStart
|
||||
),
|
||||
);
|
||||
// Package has been built, so we can get the updated application ID and
|
||||
@@ -643,6 +644,9 @@ class AndroidDevice extends Device {
|
||||
@override
|
||||
bool get supportsHotRestart => true;
|
||||
|
||||
@override
|
||||
bool get supportsFastStart => true;
|
||||
|
||||
@override
|
||||
Future<bool> stopApp(AndroidApk app) {
|
||||
final List<String> command = adbCommandForDevice(<String>['shell', 'am', 'force-stop', app.id]);
|
||||
|
||||
@@ -331,6 +331,9 @@ Future<void> buildGradleApp({
|
||||
// Don't use settings.gradle from the current project since it includes the plugins as subprojects.
|
||||
command.add('--settings-file=settings_aar.gradle');
|
||||
}
|
||||
if (androidBuildInfo.fastStart) {
|
||||
command.add('-Pfast-start=true');
|
||||
}
|
||||
command.add(assembleTask);
|
||||
|
||||
GradleHandledError detectedGradleError;
|
||||
|
||||
@@ -103,6 +103,7 @@ class AndroidBuildInfo {
|
||||
],
|
||||
this.splitPerAbi = false,
|
||||
this.shrink = false,
|
||||
this.fastStart = false,
|
||||
});
|
||||
|
||||
// The build info containing the mode and flavor.
|
||||
@@ -120,6 +121,9 @@ class AndroidBuildInfo {
|
||||
|
||||
/// The target platforms for the build.
|
||||
final Iterable<AndroidArch> targetArchs;
|
||||
|
||||
/// Whether to bootstrap an empty application.
|
||||
final bool fastStart;
|
||||
}
|
||||
|
||||
/// A summary of the compilation strategy used for Dart.
|
||||
|
||||
@@ -423,7 +423,7 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner {
|
||||
@override
|
||||
Future<OperationResult> restart({
|
||||
bool fullRestart = false,
|
||||
bool pauseAfterRestart = false,
|
||||
bool pause = false,
|
||||
String reason,
|
||||
bool benchmarkMode = false,
|
||||
}) async {
|
||||
@@ -692,7 +692,7 @@ class _DwdsResidentWebRunner extends ResidentWebRunner {
|
||||
@override
|
||||
Future<OperationResult> restart({
|
||||
bool fullRestart = false,
|
||||
bool pauseAfterRestart = false,
|
||||
bool pause = false,
|
||||
String reason,
|
||||
bool benchmarkMode = false,
|
||||
}) async {
|
||||
|
||||
@@ -31,10 +31,13 @@ abstract class AndroidAssetBundle extends Target {
|
||||
List<Source> get outputs => const <Source>[];
|
||||
|
||||
@override
|
||||
List<String> get depfiles => const <String>[
|
||||
'flutter_assets.d',
|
||||
List<String> get depfiles => <String>[
|
||||
if (_copyAssets)
|
||||
'flutter_assets.d',
|
||||
];
|
||||
|
||||
bool get _copyAssets => true;
|
||||
|
||||
@override
|
||||
Future<void> build(Environment environment) async {
|
||||
if (environment.defines[kBuildMode] == null) {
|
||||
@@ -56,8 +59,10 @@ abstract class AndroidAssetBundle extends Target {
|
||||
fs.file(isolateSnapshotData)
|
||||
.copySync(outputDirectory.childFile('isolate_snapshot_data').path);
|
||||
}
|
||||
final Depfile assetDepfile = await copyAssets(environment, outputDirectory);
|
||||
assetDepfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
|
||||
if (_copyAssets) {
|
||||
final Depfile assetDepfile = await copyAssets(environment, outputDirectory);
|
||||
assetDepfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -90,6 +95,17 @@ class DebugAndroidApplication extends AndroidAssetBundle {
|
||||
];
|
||||
}
|
||||
|
||||
/// A minimal android application that does not include assets.
|
||||
class FastStartAndroidApplication extends DebugAndroidApplication {
|
||||
const FastStartAndroidApplication();
|
||||
|
||||
@override
|
||||
String get name => 'faststart_android_application';
|
||||
|
||||
@override
|
||||
bool get _copyAssets => false;
|
||||
}
|
||||
|
||||
/// An implementation of [AndroidAssetBundle] that only includes assets.
|
||||
class AotAndroidAssetBundle extends AndroidAssetBundle {
|
||||
const AotAndroidAssetBundle();
|
||||
|
||||
@@ -37,6 +37,7 @@ const List<Target> _kDefaultTargets = <Target>[
|
||||
DebugBundleLinuxAssets(),
|
||||
WebReleaseBundle(),
|
||||
DebugAndroidApplication(),
|
||||
FastStartAndroidApplication(),
|
||||
ProfileAndroidApplication(),
|
||||
ReleaseAndroidApplication(),
|
||||
// These are one-off rules for bundle and aot compat
|
||||
|
||||
@@ -578,7 +578,7 @@ class AppDomain extends Domain {
|
||||
}
|
||||
|
||||
_inProgressHotReload = app._runInZone<OperationResult>(this, () {
|
||||
return app.restart(fullRestart: fullRestart, pauseAfterRestart: pauseAfterRestart, reason: restartReason);
|
||||
return app.restart(fullRestart: fullRestart, pause: pauseAfterRestart, reason: restartReason);
|
||||
});
|
||||
return _inProgressHotReload.whenComplete(() {
|
||||
_inProgressHotReload = null;
|
||||
@@ -945,8 +945,8 @@ class AppInstance {
|
||||
|
||||
_AppRunLogger _logger;
|
||||
|
||||
Future<OperationResult> restart({ bool fullRestart = false, bool pauseAfterRestart = false, String reason }) {
|
||||
return runner.restart(fullRestart: fullRestart, pauseAfterRestart: pauseAfterRestart, reason: reason);
|
||||
Future<OperationResult> restart({ bool fullRestart = false, bool pause = false, String reason }) {
|
||||
return runner.restart(fullRestart: fullRestart, pause: pause, reason: reason);
|
||||
}
|
||||
|
||||
Future<OperationResult> reloadMethod({ String classId, String libraryId }) {
|
||||
|
||||
@@ -187,6 +187,14 @@ class RunCommand extends RunCommandBase {
|
||||
hide: true,
|
||||
help: 'Whether to automatically invoke webOnlyInitializePlatform.',
|
||||
)
|
||||
..addFlag('fast-start',
|
||||
negatable: true,
|
||||
defaultsTo: false,
|
||||
hide: true,
|
||||
help: 'Whether to quickly bootstrap applications with a minimal app. '
|
||||
'Currently this is only supported on Android devices. This option '
|
||||
'cannot be paired with --use-application-binary.'
|
||||
)
|
||||
..addOption(FlutterOptions.kExtraFrontEndOptions, hide: true)
|
||||
..addOption(FlutterOptions.kExtraGenSnapshotOptions, hide: true)
|
||||
..addMultiOption(FlutterOptions.kEnableExperiment,
|
||||
@@ -284,6 +292,11 @@ class RunCommand extends RunCommandBase {
|
||||
if (!runningWithPrebuiltApplication) {
|
||||
await super.validateCommand();
|
||||
}
|
||||
|
||||
if (boolArg('fast-start') && runningWithPrebuiltApplication) {
|
||||
throwToolExit('--fast-start is not supported with --use-application-binary');
|
||||
}
|
||||
|
||||
devices = await findAllTargetDevices();
|
||||
if (devices == null) {
|
||||
throwToolExit(null);
|
||||
@@ -322,6 +335,9 @@ class RunCommand extends RunCommandBase {
|
||||
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
|
||||
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
|
||||
vmserviceOutFile: stringArg('vmservice-out-file'),
|
||||
// Allow forcing fast-start to off to prevent doing more work on devices that
|
||||
// don't support it.
|
||||
fastStart: boolArg('fast-start') && devices.every((Device device) => device.supportsFastStart),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -384,6 +400,12 @@ class RunCommand extends RunCommandBase {
|
||||
}
|
||||
|
||||
for (Device device in devices) {
|
||||
if (!device.supportsFastStart && boolArg('fast-start')) {
|
||||
printStatus(
|
||||
'Using --fast-start option with device ${device.name}, but this device '
|
||||
'does not support it. Overriding the setting to false.'
|
||||
);
|
||||
}
|
||||
if (await device.isLocalEmulator) {
|
||||
if (await device.supportsHardwareRendering) {
|
||||
final bool enableSoftwareRendering = boolArg('enable-software-rendering') == true;
|
||||
|
||||
@@ -420,6 +420,9 @@ abstract class Device {
|
||||
/// application.
|
||||
bool get supportsScreenshot => false;
|
||||
|
||||
/// Whether the device supports the '--fast-start' development mode.
|
||||
bool get supportsFastStart => false;
|
||||
|
||||
/// Stop an app package on the current device.
|
||||
Future<bool> stopApp(covariant ApplicationPackage app);
|
||||
|
||||
@@ -534,6 +537,7 @@ class DebuggingOptions {
|
||||
this.hostname,
|
||||
this.port,
|
||||
this.vmserviceOutFile,
|
||||
this.fastStart = false,
|
||||
}) : debuggingEnabled = true;
|
||||
|
||||
DebuggingOptions.disabled(this.buildInfo, { this.initializePlatform = true, this.port, this.hostname, this.cacheSkSL = false, })
|
||||
@@ -550,7 +554,8 @@ class DebuggingOptions {
|
||||
verboseSystemLogs = false,
|
||||
hostVmServicePort = null,
|
||||
deviceVmServicePort = null,
|
||||
vmserviceOutFile = null;
|
||||
vmserviceOutFile = null,
|
||||
fastStart = false;
|
||||
|
||||
final bool debuggingEnabled;
|
||||
|
||||
@@ -574,6 +579,7 @@ class DebuggingOptions {
|
||||
final String hostname;
|
||||
/// A file where the vmservice URL should be written after the application is started.
|
||||
final String vmserviceOutFile;
|
||||
final bool fastStart;
|
||||
|
||||
bool get hasObservatoryPort => hostVmServicePort != null;
|
||||
}
|
||||
|
||||
@@ -714,7 +714,7 @@ abstract class ResidentRunner {
|
||||
|
||||
bool get supportsRestart => false;
|
||||
|
||||
Future<OperationResult> restart({ bool fullRestart = false, bool pauseAfterRestart = false, String reason }) {
|
||||
Future<OperationResult> restart({ bool fullRestart = false, bool pause = false, String reason }) {
|
||||
final String mode = isRunningProfile ? 'profile' :
|
||||
isRunningRelease ? 'release' : 'this';
|
||||
throw '${fullRestart ? 'Restart' : 'Reload'} is not supported in $mode mode';
|
||||
|
||||
@@ -103,7 +103,7 @@ class HotRunner extends ResidentRunner {
|
||||
bool pause = false,
|
||||
}) async {
|
||||
// TODO(cbernaschina): check that isolateId is the id of the UI isolate.
|
||||
final OperationResult result = await restart(pauseAfterRestart: pause);
|
||||
final OperationResult result = await restart(pause: pause);
|
||||
if (!result.isOk) {
|
||||
throw rpc.RpcException(
|
||||
rpc_error_code.INTERNAL_ERROR,
|
||||
@@ -114,7 +114,7 @@ class HotRunner extends ResidentRunner {
|
||||
|
||||
Future<void> _restartService({ bool pause = false }) async {
|
||||
final OperationResult result =
|
||||
await restart(fullRestart: true, pauseAfterRestart: pause);
|
||||
await restart(fullRestart: true, pause: pause);
|
||||
if (!result.isOk) {
|
||||
throw rpc.RpcException(
|
||||
rpc_error_code.INTERNAL_ERROR,
|
||||
@@ -263,6 +263,18 @@ class HotRunner extends ResidentRunner {
|
||||
}
|
||||
}
|
||||
|
||||
// In fast-start mode, apps are initialized from a placeholder splashscreen
|
||||
// app. We must do a restart here to load the program and assets for the
|
||||
// real app.
|
||||
if (debuggingOptions.fastStart) {
|
||||
await restart(
|
||||
fullRestart: true,
|
||||
benchmarkMode: !debuggingOptions.startPaused,
|
||||
reason: 'restart',
|
||||
silent: true,
|
||||
);
|
||||
}
|
||||
|
||||
appStartedCompleter?.complete();
|
||||
|
||||
if (benchmarkMode) {
|
||||
@@ -408,12 +420,10 @@ class HotRunner extends ResidentRunner {
|
||||
Uri packagesUri,
|
||||
Uri assetsDirectoryUri,
|
||||
) {
|
||||
final List<Future<void>> futures = <Future<void>>[
|
||||
for (FlutterView view in device.views) view.runFromSource(entryUri, packagesUri, assetsDirectoryUri),
|
||||
];
|
||||
final Completer<void> completer = Completer<void>();
|
||||
Future.wait(futures).whenComplete(() { completer.complete(null); });
|
||||
return completer.future;
|
||||
return Future.wait(<Future<void>>[
|
||||
for (FlutterView view in device.views)
|
||||
view.runFromSource(entryUri, packagesUri, assetsDirectoryUri),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> _launchFromDevFS(String mainScript) async {
|
||||
@@ -510,9 +520,11 @@ class HotRunner extends ResidentRunner {
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
for (FlutterView view in device.views) {
|
||||
isolateNotifications.add(
|
||||
view.owner.vm.vmService.onIsolateEvent.then((Stream<ServiceEvent> serviceEvents) async {
|
||||
view.owner.vm.vmService.onIsolateEvent
|
||||
.then((Stream<ServiceEvent> serviceEvents) async {
|
||||
await for (ServiceEvent serviceEvent in serviceEvents) {
|
||||
if (serviceEvent.owner.name.contains('_spawn') && serviceEvent.kind == ServiceEvent.kIsolateExit) {
|
||||
if (serviceEvent.owner.name.contains('_spawn')
|
||||
&& serviceEvent.kind == ServiceEvent.kIsolateExit) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -573,9 +585,10 @@ class HotRunner extends ResidentRunner {
|
||||
@override
|
||||
Future<OperationResult> restart({
|
||||
bool fullRestart = false,
|
||||
bool pauseAfterRestart = false,
|
||||
String reason,
|
||||
bool benchmarkMode = false,
|
||||
bool silent = false,
|
||||
bool pause = false,
|
||||
}) async {
|
||||
String targetPlatform;
|
||||
String sdkName;
|
||||
@@ -602,8 +615,11 @@ class HotRunner extends ResidentRunner {
|
||||
emulator: emulator,
|
||||
reason: reason,
|
||||
benchmarkMode: benchmarkMode,
|
||||
silent: silent,
|
||||
);
|
||||
printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
|
||||
if (!silent) {
|
||||
printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
final OperationResult result = await _hotReloadHelper(
|
||||
@@ -611,11 +627,13 @@ class HotRunner extends ResidentRunner {
|
||||
sdkName: sdkName,
|
||||
emulator: emulator,
|
||||
reason: reason,
|
||||
pauseAfterRestart: pauseAfterRestart,
|
||||
pause: pause,
|
||||
);
|
||||
if (result.isOk) {
|
||||
final String elapsed = getElapsedAsMilliseconds(timer.elapsed);
|
||||
printStatus('${result.message} in $elapsed.');
|
||||
if (!silent) {
|
||||
printStatus('${result.message} in $elapsed.');
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -626,15 +644,19 @@ class HotRunner extends ResidentRunner {
|
||||
bool emulator,
|
||||
String reason,
|
||||
bool benchmarkMode,
|
||||
bool silent,
|
||||
}) async {
|
||||
if (!canHotRestart) {
|
||||
return OperationResult(1, 'hotRestart not supported');
|
||||
}
|
||||
final Status status = logger.startProgress(
|
||||
'Performing hot restart...',
|
||||
timeout: timeoutConfiguration.fastOperation,
|
||||
progressId: 'hot.restart',
|
||||
);
|
||||
Status status;
|
||||
if (!silent) {
|
||||
status = logger.startProgress(
|
||||
'Performing hot restart...',
|
||||
timeout: timeoutConfiguration.fastOperation,
|
||||
progressId: 'hot.restart',
|
||||
);
|
||||
}
|
||||
OperationResult result;
|
||||
String restartEvent = 'restart';
|
||||
try {
|
||||
@@ -662,7 +684,7 @@ class HotRunner extends ResidentRunner {
|
||||
emulator: emulator,
|
||||
fullRestart: true,
|
||||
reason: reason).send();
|
||||
status.cancel();
|
||||
status?.cancel();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -672,7 +694,7 @@ class HotRunner extends ResidentRunner {
|
||||
String sdkName,
|
||||
bool emulator,
|
||||
String reason,
|
||||
bool pauseAfterRestart = false,
|
||||
bool pause,
|
||||
}) async {
|
||||
final bool reloadOnTopOfSnapshot = _runningFromSnapshot;
|
||||
final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing';
|
||||
@@ -687,8 +709,8 @@ class HotRunner extends ResidentRunner {
|
||||
targetPlatform: targetPlatform,
|
||||
sdkName: sdkName,
|
||||
emulator: emulator,
|
||||
pause: pauseAfterRestart,
|
||||
reason: reason,
|
||||
pause: pause,
|
||||
onSlow: (String message) {
|
||||
status?.cancel();
|
||||
status = logger.startProgress(
|
||||
|
||||
@@ -11,12 +11,15 @@ import 'package:flutter_tools/src/application_package.dart';
|
||||
import 'package:flutter_tools/src/artifacts.dart';
|
||||
import 'package:flutter_tools/src/base/common.dart';
|
||||
import 'package:flutter_tools/src/base/context.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/user_messages.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/commands/run.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';
|
||||
@@ -56,6 +59,72 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
testUsingContext('does not support "--use-application-binary" and "--fast-start"', () async {
|
||||
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
|
||||
fs.file('pubspec.yaml').createSync();
|
||||
fs.file('.packages').createSync();
|
||||
|
||||
final RunCommand command = RunCommand();
|
||||
applyMocksToCommand(command);
|
||||
try {
|
||||
await createTestCommandRunner(command).run(<String>[
|
||||
'run',
|
||||
'--use-application-binary=app/bar/faz',
|
||||
'--fast-start',
|
||||
'--no-pub',
|
||||
'--show-test-device',
|
||||
]);
|
||||
fail('Expect exception');
|
||||
} catch (e) {
|
||||
expect(e.toString(), contains('--fast-start is not supported with --use-application-binary'));
|
||||
}
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem(),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testUsingContext('Forces fast start off for devices that do not support it', () async {
|
||||
final MockDevice mockDevice = MockDevice(TargetPlatform.android_arm);
|
||||
when(mockDevice.name).thenReturn('mockdevice');
|
||||
when(mockDevice.supportsFastStart).thenReturn(false);
|
||||
when(mockDevice.supportsHotReload).thenReturn(true);
|
||||
when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async => false);
|
||||
when(deviceManager.hasSpecifiedAllDevices).thenReturn(false);
|
||||
when(deviceManager.findTargetDevices(any)).thenAnswer((Invocation invocation) {
|
||||
return Future<List<Device>>.value(<Device>[mockDevice]);
|
||||
});
|
||||
when(deviceManager.getDevices()).thenAnswer((Invocation invocation) {
|
||||
return Stream<Device>.value(mockDevice);
|
||||
});
|
||||
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
|
||||
fs.file('pubspec.yaml').createSync();
|
||||
fs.file('.packages').createSync();
|
||||
|
||||
final RunCommand command = RunCommand();
|
||||
applyMocksToCommand(command);
|
||||
try {
|
||||
await createTestCommandRunner(command).run(<String>[
|
||||
'run',
|
||||
'--fast-start',
|
||||
'--no-pub',
|
||||
]);
|
||||
fail('Expect exception');
|
||||
} catch (e) {
|
||||
expect(e, isInstanceOf<ToolExit>());
|
||||
}
|
||||
|
||||
final BufferLogger bufferLogger = logger as BufferLogger;
|
||||
expect(bufferLogger.statusText, contains(
|
||||
'Using --fast-start option with device mockdevice, but this device '
|
||||
'does not support it. Overriding the setting to false.'
|
||||
));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem(),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
DeviceManager: () => MockDeviceManager(),
|
||||
});
|
||||
|
||||
|
||||
group('run app', () {
|
||||
MemoryFileSystem fs;
|
||||
MockArtifacts mockArtifacts;
|
||||
@@ -166,6 +235,7 @@ void main() {
|
||||
final MockDevice mockDevice = MockDevice(TargetPlatform.ios);
|
||||
when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) => Future<bool>.value(false));
|
||||
when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(MockDeviceLogReader());
|
||||
when(mockDevice.supportsFastStart).thenReturn(true);
|
||||
// App fails to start because we're only interested in usage
|
||||
when(mockDevice.startApp(
|
||||
any,
|
||||
@@ -497,6 +567,9 @@ class FakeDevice extends Fake implements Device {
|
||||
@override
|
||||
bool get supportsHotReload => false;
|
||||
|
||||
@override
|
||||
bool get supportsFastStart => false;
|
||||
|
||||
@override
|
||||
Future<String> get sdkNameAndVersion => Future<String>.value('');
|
||||
|
||||
|
||||
@@ -154,6 +154,41 @@ void main() {
|
||||
expect(onAppStart.isCompleted, true);
|
||||
}));
|
||||
|
||||
test('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async {
|
||||
when(mockDevice.supportsHotRestart).thenReturn(true);
|
||||
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
|
||||
return 'Example';
|
||||
});
|
||||
when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
|
||||
return TargetPlatform.android_arm;
|
||||
});
|
||||
when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async {
|
||||
return false;
|
||||
});
|
||||
residentRunner = HotRunner(
|
||||
<FlutterDevice>[
|
||||
mockFlutterDevice,
|
||||
],
|
||||
stayResident: false,
|
||||
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, fastStart: true, startPaused: true),
|
||||
);
|
||||
final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
|
||||
final Completer<void> onAppStart = Completer<void>.sync();
|
||||
final Future<int> result = residentRunner.attach(
|
||||
appStartedCompleter: onAppStart,
|
||||
connectionInfoCompleter: onConnectionInfo,
|
||||
);
|
||||
final Future<DebugConnectionInfo> connectionInfo = onConnectionInfo.future;
|
||||
|
||||
expect(await result, 0);
|
||||
|
||||
verify(mockFlutterDevice.initLogReader()).called(1);
|
||||
|
||||
expect(onConnectionInfo.isCompleted, true);
|
||||
expect((await connectionInfo).baseUri, 'foo://bar');
|
||||
expect(onAppStart.isCompleted, true);
|
||||
}));
|
||||
|
||||
test('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async {
|
||||
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
|
||||
return 'Example';
|
||||
|
||||
@@ -497,7 +497,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
|
||||
// fast.
|
||||
unawaited(_process.exitCode.then((_) {
|
||||
if (!prematureExitGuard.isCompleted) {
|
||||
prematureExitGuard.completeError('Process existed prematurely: ${args.join(' ')}');
|
||||
prematureExitGuard.completeError('Process existed prematurely: ${args.join(' ')}: $_errorBuffer');
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user