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