Commit 030abfd4 authored by Mikkel Nygaard Ravn's avatar Mikkel Nygaard Ravn Committed by GitHub

Provide user feedback on slow Gradle operations (#11015)

parent a106f504
......@@ -360,7 +360,7 @@ class AndroidDevice extends Device {
);
// Package has been built, so we can get the updated application ID and
// activity name from the .apk.
package = new AndroidApk.fromCurrentDirectory();
package = await AndroidApk.fromCurrentDirectory();
}
printTrace("Stopping app '${package.name}' on $name.");
......
......@@ -25,6 +25,7 @@ const String gradleAppOutDirV1 = 'android/app/build/outputs/apk';
const String gradleVersion = '3.3';
String _cachedGradleAppOutDirV2;
String _cachedGradleExecutable;
enum FlutterPluginVersion {
none,
......@@ -57,7 +58,7 @@ FlutterPluginVersion get flutterPluginVersion {
return FlutterPluginVersion.none;
}
String getGradleAppOut() {
Future<String> getGradleAppOut() async {
switch (flutterPluginVersion) {
case FlutterPluginVersion.none:
// Fall through. Pretend we're v1, and just go with it.
......@@ -66,27 +67,29 @@ String getGradleAppOut() {
case FlutterPluginVersion.managed:
// Fall through. The managed plugin matches plugin v2 for now.
case FlutterPluginVersion.v2:
return '${getGradleAppOutDirV2()}/app.apk';
return '${await _getGradleAppOutDirV2()}/app.apk';
}
return null;
}
String getGradleAppOutDirV2() {
_cachedGradleAppOutDirV2 ??= _calculateGradleAppOutDirV2();
Future<String> _getGradleAppOutDirV2() async {
_cachedGradleAppOutDirV2 ??= await _calculateGradleAppOutDirV2();
return _cachedGradleAppOutDirV2;
}
// Note: this call takes about a second to complete.
String _calculateGradleAppOutDirV2() {
final String gradle = ensureGradle();
// Note: Dependencies are resolved and possibly downloaded as a side-effect
// of calculating the app properties using Gradle. This may take minutes.
Future<String> _calculateGradleAppOutDirV2() async {
final String gradle = await _ensureGradle();
updateLocalProperties();
try {
final String properties = runCheckedSync(
final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true);
final RunResult runResult = await runCheckedAsync(
<String>[gradle, 'app:properties'],
workingDirectory: 'android',
hideStdout: true,
environment: _gradleEnv,
);
final String properties = runResult.stdout.trim();
String buildDir = properties
.split('\n')
.firstWhere((String s) => s.startsWith('buildDir: '))
......@@ -97,6 +100,7 @@ String _calculateGradleAppOutDirV2() {
// Relativize path, snip current directory + separating '/'.
buildDir = buildDir.substring(currentDirectory.length + 1);
}
status.stop();
return '$buildDir/outputs/apk';
} catch (e) {
printError('Error running gradle: $e');
......@@ -105,7 +109,7 @@ String _calculateGradleAppOutDirV2() {
return gradleAppOutDirV1;
}
String locateProjectGradlew({ bool ensureExecutable: true }) {
String _locateProjectGradlew({ bool ensureExecutable: true }) {
final String path = fs.path.join(
'android', platform.isWindows ? 'gradlew.bat' : 'gradlew'
);
......@@ -120,12 +124,27 @@ String locateProjectGradlew({ bool ensureExecutable: true }) {
}
}
String ensureGradle() {
String gradle = locateProjectGradlew();
Future<String> _ensureGradle() async {
_cachedGradleExecutable ??= await _initializeGradle();
return _cachedGradleExecutable;
}
// Note: Gradle may be bootstrapped and possibly downloaded as a side-effect
// of validating the Gradle executable. This may take several seconds.
Future<String> _initializeGradle() async {
final Status status = logger.startProgress('Initializing Gradle...', expectSlowOperation: true);
String gradle = _locateProjectGradlew();
if (gradle == null) {
_injectGradleWrapper();
gradle = locateProjectGradlew();
gradle = _locateProjectGradlew();
}
if (gradle == null)
throwToolExit('Unable to locate gradlew script');
printTrace('Using gradle from $gradle.');
// Validates the Gradle executable by asking for its version.
// Makes Gradle Wrapper download and install Gradle distribution, if needed.
await runCheckedAsync(<String>[gradle, '-v'], environment: _gradleEnv);
status.stop();
return gradle;
}
......@@ -183,24 +202,24 @@ Future<Null> buildGradleProject(BuildMode buildMode, String target, String kerne
injectPlugins();
final String gradle = ensureGradle();
final String gradle = await _ensureGradle();
switch (flutterPluginVersion) {
case FlutterPluginVersion.none:
// Fall through. Pretend it's v1, and just go for it.
case FlutterPluginVersion.v1:
return buildGradleProjectV1(gradle);
return _buildGradleProjectV1(gradle);
case FlutterPluginVersion.managed:
// Fall through. Managed plugin builds the same way as plugin v2.
case FlutterPluginVersion.v2:
return buildGradleProjectV2(gradle, buildModeName, target, kernelPath);
return _buildGradleProjectV2(gradle, buildModeName, target, kernelPath);
}
}
Future<Null> buildGradleProjectV1(String gradle) async {
// Run 'gradle build'.
final Status status = logger.startProgress('Running \'gradle build\'...', expectSlowOperation: true);
final int exitcode = await runCommandAndStreamOutput(
Future<Null> _buildGradleProjectV1(String gradle) async {
// Run 'gradlew build'.
final Status status = logger.startProgress('Running \'gradlew build\'...', expectSlowOperation: true);
final int exitCode = await runCommandAndStreamOutput(
<String>[fs.file(gradle).absolute.path, 'build'],
workingDirectory: 'android',
allowReentrantFlutter: true,
......@@ -208,8 +227,8 @@ Future<Null> buildGradleProjectV1(String gradle) async {
);
status.stop();
if (exitcode != 0)
throwToolExit('Gradle build failed: $exitcode', exitCode: exitcode);
if (exitCode != 0)
throwToolExit('Gradle build failed: $exitCode', exitCode: exitCode);
final File apkFile = fs.file(gradleAppOutV1);
printStatus('Built $gradleAppOutV1 (${getSizeAsMB(apkFile.lengthSync())}).');
......@@ -226,10 +245,10 @@ File findApkFile(String buildDirectory, String buildModeName) {
return null;
}
Future<Null> buildGradleProjectV2(String gradle, String buildModeName, String target, String kernelPath) async {
Future<Null> _buildGradleProjectV2(String gradle, String buildModeName, String target, String kernelPath) async {
final String assembleTask = "assemble${toTitleCase(buildModeName)}";
// Run 'gradle assemble<BuildMode>'.
// Run 'gradlew assemble<BuildMode>'.
final Status status = logger.startProgress('Running \'gradlew $assembleTask\'...', expectSlowOperation: true);
final String gradlePath = fs.file(gradle).absolute.path;
final List<String> command = <String>[gradlePath];
......@@ -258,7 +277,7 @@ Future<Null> buildGradleProjectV2(String gradle, String buildModeName, String ta
if (exitcode != 0)
throwToolExit('Gradle build failed: $exitcode', exitCode: exitcode);
final String buildDirectory = getGradleAppOutDirV2();
final String buildDirectory = await _getGradleAppOutDirV2();
final File apkFile = findApkFile(buildDirectory, buildModeName);
if (apkFile == null)
throwToolExit('Gradle build failed to produce an Android package.');
......
......@@ -2,6 +2,8 @@
// 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:meta/meta.dart' show required;
import 'package:xml/xml.dart' as xml;
......@@ -78,21 +80,21 @@ class AndroidApk extends ApplicationPackage {
}
/// Creates a new AndroidApk based on the information in the Android manifest.
factory AndroidApk.fromCurrentDirectory() {
static Future<AndroidApk> fromCurrentDirectory() async {
String manifestPath;
String apkPath;
if (isProjectUsingGradle()) {
if (fs.file(getGradleAppOut()).existsSync()) {
final String apkPath = await getGradleAppOut();
if (fs.file(apkPath).existsSync()) {
// Grab information from the .apk. The gradle build script might alter
// the application Id, so we need to look at what was actually built.
return new AndroidApk.fromApk(getGradleAppOut());
return new AndroidApk.fromApk(apkPath);
}
// The .apk hasn't been built yet, so we work with what we have. The run
// command will grab a new AndroidApk after building, to get the updated
// IDs.
manifestPath = gradleManifestPath;
apkPath = getGradleAppOut();
} else {
manifestPath = fs.path.join('android', 'AndroidManifest.xml');
apkPath = fs.path.join(getAndroidBuildDirectory(), 'app.apk');
......@@ -251,15 +253,15 @@ class PrebuiltIOSApp extends IOSApp {
String get _bundlePath => bundleDir.path;
}
ApplicationPackage getApplicationPackageForPlatform(TargetPlatform platform, {
Future<ApplicationPackage> getApplicationPackageForPlatform(TargetPlatform platform, {
String applicationBinary
}) {
}) async {
switch (platform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
return applicationBinary == null
? new AndroidApk.fromCurrentDirectory()
? await AndroidApk.fromCurrentDirectory()
: new AndroidApk.fromApk(applicationBinary);
case TargetPlatform.ios:
return applicationBinary == null
......@@ -281,12 +283,12 @@ class ApplicationPackageStore {
ApplicationPackageStore({ this.android, this.iOS });
ApplicationPackage getPackageForPlatform(TargetPlatform platform) {
Future<ApplicationPackage> getPackageForPlatform(TargetPlatform platform) async {
switch (platform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
android ??= new AndroidApk.fromCurrentDirectory();
android ??= await AndroidApk.fromCurrentDirectory();
return android;
case TargetPlatform.ios:
iOS ??= new IOSApp.fromCurrentDirectory();
......
......@@ -45,7 +45,7 @@ class BuildIOSCommand extends BuildSubCommand {
if (getCurrentHostPlatform() != HostPlatform.darwin_x64)
throwToolExit('Building for iOS is only supported on the Mac.');
final BuildableIOSApp app = applicationPackages.getPackageForPlatform(TargetPlatform.ios);
final BuildableIOSApp app = await applicationPackages.getPackageForPlatform(TargetPlatform.ios);
if (app == null)
throwToolExit('Application not configured for iOS');
......
......@@ -229,7 +229,7 @@ Future<LaunchResult> _startApp(DriveCommand command) async {
await appStopper(command);
printTrace('Installing application package.');
final ApplicationPackage package = command.applicationPackages
final ApplicationPackage package = await command.applicationPackages
.getPackageForPlatform(await command.device.targetPlatform);
if (await command.device.isAppInstalled(package))
await command.device.uninstallApp(package);
......@@ -304,7 +304,7 @@ void restoreAppStopper() {
Future<bool> _stopApp(DriveCommand command) async {
printTrace('Stopping application.');
final ApplicationPackage package = command.applicationPackages.getPackageForPlatform(await command.device.targetPlatform);
final ApplicationPackage package = await command.applicationPackages.getPackageForPlatform(await command.device.targetPlatform);
final bool stopped = await command.device.stopApp(package);
await command._deviceLogSubscription?.cancel();
return stopped;
......
......@@ -31,7 +31,7 @@ class InstallCommand extends FlutterCommand {
@override
Future<Null> runCommand() async {
final ApplicationPackage package = applicationPackages.getPackageForPlatform(await device.targetPlatform);
final ApplicationPackage package = await applicationPackages.getPackageForPlatform(await device.targetPlatform);
Cache.releaseLockEarly();
......
......@@ -32,7 +32,7 @@ class StopCommand extends FlutterCommand {
@override
Future<Null> runCommand() async {
final TargetPlatform targetPlatform = await device.targetPlatform;
final ApplicationPackage app = applicationPackages.getPackageForPlatform(targetPlatform);
final ApplicationPackage app = await applicationPackages.getPackageForPlatform(targetPlatform);
if (app == null) {
final String platformName = getNameForTargetPlatform(targetPlatform);
throwToolExit('No Flutter application for $platformName found in the current directory.');
......
......@@ -179,7 +179,7 @@ void _writeIOSPluginRegistry(String directory, List<Plugin> plugins) {
}
/// Finds Flutter plugins in the pubspec.yaml, creates platform injection
/// registries classes and add them to the build depedencies.
/// registries classes and add them to the build dependencies.
///
/// Returns whether any Flutter plugins are added.
bool injectPlugins({String directory}) {
......
......@@ -210,7 +210,7 @@ class FlutterDevice {
printStatus('Launching ${getDisplayPath(hotRunner.mainPath)} on ${device.name} in $modeName mode...');
final TargetPlatform targetPlatform = await device.targetPlatform;
package = getApplicationPackageForPlatform(
package = await getApplicationPackageForPlatform(
targetPlatform,
applicationBinary: hotRunner.applicationBinary
);
......@@ -261,7 +261,7 @@ class FlutterDevice {
bool shouldBuild: true,
}) async {
final TargetPlatform targetPlatform = await device.targetPlatform;
package = getApplicationPackageForPlatform(
package = await getApplicationPackageForPlatform(
targetPlatform,
applicationBinary: coldRunner.applicationBinary
);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment