diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index bd6dbf53798..984a535948a 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -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."); diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 2310c834bca..72e52202d12 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -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 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 _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 _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( [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 _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 _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([gradle, '-v'], environment: _gradleEnv); + status.stop(); return gradle; } @@ -183,24 +202,24 @@ Future 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 buildGradleProjectV1(String gradle) async { - // Run 'gradle build'. - final Status status = logger.startProgress('Running \'gradle build\'...', expectSlowOperation: true); - final int exitcode = await runCommandAndStreamOutput( +Future _buildGradleProjectV1(String gradle) async { + // Run 'gradlew build'. + final Status status = logger.startProgress('Running \'gradlew build\'...', expectSlowOperation: true); + final int exitCode = await runCommandAndStreamOutput( [fs.file(gradle).absolute.path, 'build'], workingDirectory: 'android', allowReentrantFlutter: true, @@ -208,8 +227,8 @@ Future 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 buildGradleProjectV2(String gradle, String buildModeName, String target, String kernelPath) async { +Future _buildGradleProjectV2(String gradle, String buildModeName, String target, String kernelPath) async { final String assembleTask = "assemble${toTitleCase(buildModeName)}"; - // Run 'gradle assemble'. + // Run 'gradlew assemble'. final Status status = logger.startProgress('Running \'gradlew $assembleTask\'...', expectSlowOperation: true); final String gradlePath = fs.file(gradle).absolute.path; final List command = [gradlePath]; @@ -258,7 +277,7 @@ Future 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.'); diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart index 09b87375c79..22665ee24ae 100644 --- a/packages/flutter_tools/lib/src/application_package.dart +++ b/packages/flutter_tools/lib/src/application_package.dart @@ -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 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 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 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(); diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index eb72d4ae01e..cfc099273a3 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -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'); diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart index adbfd680136..91f65f7a480 100644 --- a/packages/flutter_tools/lib/src/commands/drive.dart +++ b/packages/flutter_tools/lib/src/commands/drive.dart @@ -229,7 +229,7 @@ Future _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 _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; diff --git a/packages/flutter_tools/lib/src/commands/install.dart b/packages/flutter_tools/lib/src/commands/install.dart index 3651c01d1b6..9ae31a9412d 100644 --- a/packages/flutter_tools/lib/src/commands/install.dart +++ b/packages/flutter_tools/lib/src/commands/install.dart @@ -31,7 +31,7 @@ class InstallCommand extends FlutterCommand { @override Future runCommand() async { - final ApplicationPackage package = applicationPackages.getPackageForPlatform(await device.targetPlatform); + final ApplicationPackage package = await applicationPackages.getPackageForPlatform(await device.targetPlatform); Cache.releaseLockEarly(); diff --git a/packages/flutter_tools/lib/src/commands/stop.dart b/packages/flutter_tools/lib/src/commands/stop.dart index 836538f361c..6cc4138ea87 100644 --- a/packages/flutter_tools/lib/src/commands/stop.dart +++ b/packages/flutter_tools/lib/src/commands/stop.dart @@ -32,7 +32,7 @@ class StopCommand extends FlutterCommand { @override Future 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.'); diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index cbfb78b74e3..0bc524d8b0e 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart @@ -179,7 +179,7 @@ void _writeIOSPluginRegistry(String directory, List 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}) { diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index da74fef6475..291398da9e8 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -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 );