From 97a9f2ae93dfad87f967075822327a5159b9956b Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Fri, 12 Feb 2021 14:24:13 -0800 Subject: [PATCH] [flutter_tools] move gradle helper methods into AndroidBuilder class body, split unit tests (#75931) --- .../lib/src/android/android_builder.dart | 94 +- .../flutter_tools/lib/src/android/gradle.dart | 1046 ++++++----- .../flutter_tools/lib/src/context_runner.dart | 3 + .../android/android_gradle_builder_test.dart | 1652 +++++++++++++++++ .../general.shard/android/gradle_test.dart | 1618 +--------------- 5 files changed, 2229 insertions(+), 2184 deletions(-) create mode 100644 packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart diff --git a/packages/flutter_tools/lib/src/android/android_builder.dart b/packages/flutter_tools/lib/src/android/android_builder.dart index c35ad1b225..3b7c70640e 100644 --- a/packages/flutter_tools/lib/src/android/android_builder.dart +++ b/packages/flutter_tools/lib/src/android/android_builder.dart @@ -6,21 +6,15 @@ import 'package:meta/meta.dart'; -import '../android/gradle_errors.dart'; import '../base/context.dart'; -import '../base/file_system.dart'; import '../build_info.dart'; -import '../globals.dart' as globals; import '../project.dart'; -import 'gradle.dart'; /// The builder in the current context. AndroidBuilder get androidBuilder { - return context.get() ?? const _AndroidBuilderImpl(); + return context.get(); } -/// Provides the methods to build Android artifacts. -// TODO(egarciad): https://github.com/flutter/flutter/issues/43863 abstract class AndroidBuilder { const AndroidBuilder(); /// Builds an AAR artifact. @@ -46,89 +40,3 @@ abstract class AndroidBuilder { @required String target, }); } - -/// Default implementation of [AarBuilder]. -class _AndroidBuilderImpl extends AndroidBuilder { - const _AndroidBuilderImpl(); - - /// Builds the AAR and POM files for the current Flutter module or plugin. - @override - Future buildAar({ - @required FlutterProject project, - @required Set androidBuildInfo, - @required String target, - @required String outputDirectoryPath, - @required String buildNumber, - }) async { - try { - Directory outputDirectory = - globals.fs.directory(outputDirectoryPath ?? project.android.buildDirectory); - if (project.isModule) { - // Module projects artifacts are located in `build/host`. - outputDirectory = outputDirectory.childDirectory('host'); - } - for (final AndroidBuildInfo androidBuildInfo in androidBuildInfo) { - await buildGradleAar( - project: project, - androidBuildInfo: androidBuildInfo, - target: target, - outputDirectory: outputDirectory, - buildNumber: buildNumber, - ); - } - printHowToConsumeAar( - buildModes: androidBuildInfo - .map((AndroidBuildInfo androidBuildInfo) { - return androidBuildInfo.buildInfo.modeName; - }).toSet(), - androidPackage: project.manifest.androidPackage, - repoDirectory: getRepoDirectory(outputDirectory), - buildNumber: buildNumber, - logger: globals.logger, - fileSystem: globals.fs, - ); - } finally { - globals.androidSdk?.reinitialize(); - } - } - - /// Builds the APK. - @override - Future buildApk({ - @required FlutterProject project, - @required AndroidBuildInfo androidBuildInfo, - @required String target, - }) async { - try { - await buildGradleApp( - project: project, - androidBuildInfo: androidBuildInfo, - target: target, - isBuildingBundle: false, - localGradleErrors: gradleErrors, - ); - } finally { - globals.androidSdk?.reinitialize(); - } - } - - /// Builds the App Bundle. - @override - Future buildAab({ - @required FlutterProject project, - @required AndroidBuildInfo androidBuildInfo, - @required String target, - }) async { - try { - await buildGradleApp( - project: project, - androidBuildInfo: androidBuildInfo, - target: target, - isBuildingBundle: true, - localGradleErrors: gradleErrors, - ); - } finally { - globals.androidSdk?.reinitialize(); - } - } -} diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 329fe5281f..27505184ff 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -24,6 +24,7 @@ import '../flutter_manifest.dart'; import '../globals.dart' as globals; import '../project.dart'; import '../reporting/reporting.dart'; +import 'android_builder.dart'; import 'gradle_errors.dart'; import 'gradle_utils.dart'; @@ -202,466 +203,608 @@ void createSettingsAarGradle(Directory androidDirectory) { globals.printStatus('$successMark `$newSettingsRelativeFile` created successfully.'); } -/// Builds an app. -/// -/// * [project] is typically [FlutterProject.current()]. -/// * [androidBuildInfo] is the build configuration. -/// * [target] is the target dart entry point. Typically, `lib/main.dart`. -/// * If [isBuildingBundle] is `true`, then the output artifact is an `*.aab`, -/// otherwise the output artifact is an `*.apk`. -/// * The plugins are built as AARs if [shouldBuildPluginAsAar] is `true`. This isn't set by default -/// because it makes the build slower proportional to the number of plugins. -/// * [retries] is the max number of build retries in case one of the [GradleHandledError] handler -/// returns [GradleBuildStatus.retry] or [GradleBuildStatus.retryWithAarPlugins]. -Future buildGradleApp({ - @required FlutterProject project, - @required AndroidBuildInfo androidBuildInfo, - @required String target, - @required bool isBuildingBundle, - @required List localGradleErrors, - bool shouldBuildPluginAsAar = false, - int retries = 1, -}) async { - assert(project != null); - assert(androidBuildInfo != null); - assert(target != null); - assert(isBuildingBundle != null); - assert(localGradleErrors != null); - assert(globals.androidSdk != null); - - if (!project.android.isUsingGradle) { - _exitWithProjectNotUsingGradleMessage(); - } - if (!_isSupportedVersion(project.android)) { - _exitWithUnsupportedProjectMessage(); - } - final Directory buildDirectory = project.android.buildDirectory; - - final bool usesAndroidX = isAppUsingAndroidX(project.android.hostAppGradleRoot); - if (usesAndroidX) { - BuildEvent('app-using-android-x', flutterUsage: globals.flutterUsage).send(); - } else if (!usesAndroidX) { - BuildEvent('app-not-using-android-x', flutterUsage: globals.flutterUsage).send(); - globals.printStatus("$warningMark Your app isn't using AndroidX.", emphasis: true); - globals.printStatus( - 'To avoid potential build failures, you can quickly migrate your app ' - 'by following the steps on https://goo.gl/CP92wY .', - indent: 4, - ); - } - // The default Gradle script reads the version name and number - // from the local.properties file. - updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo); - - if (shouldBuildPluginAsAar) { - // Create a settings.gradle that doesn't import the plugins as subprojects. - createSettingsAarGradle(project.android.hostAppGradleRoot); - await buildPluginsAsAar( - project, - androidBuildInfo, - buildDirectory: buildDirectory.childDirectory('app'), - ); - } - - final BuildInfo buildInfo = androidBuildInfo.buildInfo; - final String assembleTask = isBuildingBundle - ? getBundleTaskFor(buildInfo) - : getAssembleTaskFor(buildInfo); - - final Status status = globals.logger.startProgress( - "Running Gradle task '$assembleTask'...", - multilineOutput: true, - ); - - final List command = [ - gradleUtils.getExecutable(project), - ]; - if (globals.logger.isVerbose) { - command.add('-Pverbose=true'); - } else { - command.add('-q'); - } - if (!buildInfo.androidGradleDaemon) { - command.add('--no-daemon'); - } - if (globals.artifacts is LocalEngineArtifacts) { - final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts; - final Directory localEngineRepo = _getLocalEngineRepo( - engineOutPath: localEngineArtifacts.engineOutPath, - androidBuildInfo: androidBuildInfo, - ); - globals.printTrace( - 'Using local engine: ${localEngineArtifacts.engineOutPath}\n' - 'Local Maven repo: ${localEngineRepo.path}' - ); - command.add('-Plocal-engine-repo=${localEngineRepo.path}'); - command.add('-Plocal-engine-build-mode=${buildInfo.modeName}'); - command.add('-Plocal-engine-out=${localEngineArtifacts.engineOutPath}'); - command.add('-Ptarget-platform=${_getTargetPlatformByLocalEnginePath( - localEngineArtifacts.engineOutPath)}'); - } else if (androidBuildInfo.targetArchs.isNotEmpty) { - final String targetPlatforms = androidBuildInfo - .targetArchs - .map(getPlatformNameForAndroidArch).join(','); - command.add('-Ptarget-platform=$targetPlatforms'); - } - if (target != null) { - command.add('-Ptarget=$target'); - } - command.addAll(androidBuildInfo.buildInfo.toGradleConfig()); - - if (buildInfo.fileSystemRoots != null && buildInfo.fileSystemRoots.isNotEmpty) { - command.add('-Pfilesystem-roots=${buildInfo.fileSystemRoots.join('|')}'); - } - if (buildInfo.fileSystemScheme != null) { - command.add('-Pfilesystem-scheme=${buildInfo.fileSystemScheme}'); - } - if (androidBuildInfo.splitPerAbi) { - command.add('-Psplit-per-abi=true'); - } - if (shouldBuildPluginAsAar) { - // Pass a system flag instead of a project flag, so this flag can be - // read from include_flutter.groovy. - command.add('-Dbuild-plugins-as-aars=true'); - // 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; - String detectedGradleErrorLine; - String consumeLog(String line) { - // This message was removed from first-party plugins, - // but older plugin versions still display this message. - if (androidXPluginWarningRegex.hasMatch(line)) { - // Don't pipe. - return null; +/// An implementation of the [AndroidBuilder] that delegates to gradle. +class AndroidGradleBuilder implements AndroidBuilder { + /// Builds the AAR and POM files for the current Flutter module or plugin. + @override + Future buildAar({ + @required FlutterProject project, + @required Set androidBuildInfo, + @required String target, + @required String outputDirectoryPath, + @required String buildNumber, + }) async { + try { + Directory outputDirectory = + globals.fs.directory(outputDirectoryPath ?? project.android.buildDirectory); + if (project.isModule) { + // Module projects artifacts are located in `build/host`. + outputDirectory = outputDirectory.childDirectory('host'); + } + for (final AndroidBuildInfo androidBuildInfo in androidBuildInfo) { + await buildGradleAar( + project: project, + androidBuildInfo: androidBuildInfo, + target: target, + outputDirectory: outputDirectory, + buildNumber: buildNumber, + ); + } + printHowToConsumeAar( + buildModes: androidBuildInfo + .map((AndroidBuildInfo androidBuildInfo) { + return androidBuildInfo.buildInfo.modeName; + }).toSet(), + androidPackage: project.manifest.androidPackage, + repoDirectory: getRepoDirectory(outputDirectory), + buildNumber: buildNumber, + logger: globals.logger, + fileSystem: globals.fs, + ); + } finally { + globals.androidSdk?.reinitialize(); } - if (detectedGradleError != null) { + } + + /// Builds the APK. + @override + Future buildApk({ + @required FlutterProject project, + @required AndroidBuildInfo androidBuildInfo, + @required String target, + }) async { + try { + await buildGradleApp( + project: project, + androidBuildInfo: androidBuildInfo, + target: target, + isBuildingBundle: false, + localGradleErrors: gradleErrors, + ); + } finally { + globals.androidSdk?.reinitialize(); + } + } + + /// Builds the App Bundle. + @override + Future buildAab({ + @required FlutterProject project, + @required AndroidBuildInfo androidBuildInfo, + @required String target, + }) async { + try { + await buildGradleApp( + project: project, + androidBuildInfo: androidBuildInfo, + target: target, + isBuildingBundle: true, + localGradleErrors: gradleErrors, + ); + } finally { + globals.androidSdk?.reinitialize(); + } + } + + /// Builds an app. + /// + /// * [project] is typically [FlutterProject.current()]. + /// * [androidBuildInfo] is the build configuration. + /// * [target] is the target dart entry point. Typically, `lib/main.dart`. + /// * If [isBuildingBundle] is `true`, then the output artifact is an `*.aab`, + /// otherwise the output artifact is an `*.apk`. + /// * The plugins are built as AARs if [shouldBuildPluginAsAar] is `true`. This isn't set by default + /// because it makes the build slower proportional to the number of plugins. + /// * [retries] is the max number of build retries in case one of the [GradleHandledError] handler + /// returns [GradleBuildStatus.retry] or [GradleBuildStatus.retryWithAarPlugins]. + Future buildGradleApp({ + @required FlutterProject project, + @required AndroidBuildInfo androidBuildInfo, + @required String target, + @required bool isBuildingBundle, + @required List localGradleErrors, + bool shouldBuildPluginAsAar = false, + int retries = 1, + }) async { + assert(project != null); + assert(androidBuildInfo != null); + assert(target != null); + assert(isBuildingBundle != null); + assert(localGradleErrors != null); + assert(globals.androidSdk != null); + + if (!project.android.isUsingGradle) { + _exitWithProjectNotUsingGradleMessage(); + } + if (!_isSupportedVersion(project.android)) { + _exitWithUnsupportedProjectMessage(); + } + final Directory buildDirectory = project.android.buildDirectory; + + final bool usesAndroidX = isAppUsingAndroidX(project.android.hostAppGradleRoot); + if (usesAndroidX) { + BuildEvent('app-using-android-x', flutterUsage: globals.flutterUsage).send(); + } else if (!usesAndroidX) { + BuildEvent('app-not-using-android-x', flutterUsage: globals.flutterUsage).send(); + globals.printStatus("$warningMark Your app isn't using AndroidX.", emphasis: true); + globals.printStatus( + 'To avoid potential build failures, you can quickly migrate your app ' + 'by following the steps on https://goo.gl/CP92wY .', + indent: 4, + ); + } + // The default Gradle script reads the version name and number + // from the local.properties file. + updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo); + + if (shouldBuildPluginAsAar) { + // Create a settings.gradle that doesn't import the plugins as subprojects. + createSettingsAarGradle(project.android.hostAppGradleRoot); + await buildPluginsAsAar( + project, + androidBuildInfo, + buildDirectory: buildDirectory.childDirectory('app'), + ); + } + + final BuildInfo buildInfo = androidBuildInfo.buildInfo; + final String assembleTask = isBuildingBundle + ? getBundleTaskFor(buildInfo) + : getAssembleTaskFor(buildInfo); + + final Status status = globals.logger.startProgress( + "Running Gradle task '$assembleTask'...", + multilineOutput: true, + ); + + final List command = [ + gradleUtils.getExecutable(project), + ]; + if (globals.logger.isVerbose) { + command.add('-Pverbose=true'); + } else { + command.add('-q'); + } + if (!buildInfo.androidGradleDaemon) { + command.add('--no-daemon'); + } + if (globals.artifacts is LocalEngineArtifacts) { + final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts; + final Directory localEngineRepo = _getLocalEngineRepo( + engineOutPath: localEngineArtifacts.engineOutPath, + androidBuildInfo: androidBuildInfo, + ); + globals.printTrace( + 'Using local engine: ${localEngineArtifacts.engineOutPath}\n' + 'Local Maven repo: ${localEngineRepo.path}' + ); + command.add('-Plocal-engine-repo=${localEngineRepo.path}'); + command.add('-Plocal-engine-build-mode=${buildInfo.modeName}'); + command.add('-Plocal-engine-out=${localEngineArtifacts.engineOutPath}'); + command.add('-Ptarget-platform=${_getTargetPlatformByLocalEnginePath( + localEngineArtifacts.engineOutPath)}'); + } else if (androidBuildInfo.targetArchs.isNotEmpty) { + final String targetPlatforms = androidBuildInfo + .targetArchs + .map(getPlatformNameForAndroidArch).join(','); + command.add('-Ptarget-platform=$targetPlatforms'); + } + if (target != null) { + command.add('-Ptarget=$target'); + } + command.addAll(androidBuildInfo.buildInfo.toGradleConfig()); + + if (buildInfo.fileSystemRoots != null && buildInfo.fileSystemRoots.isNotEmpty) { + command.add('-Pfilesystem-roots=${buildInfo.fileSystemRoots.join('|')}'); + } + if (buildInfo.fileSystemScheme != null) { + command.add('-Pfilesystem-scheme=${buildInfo.fileSystemScheme}'); + } + if (androidBuildInfo.splitPerAbi) { + command.add('-Psplit-per-abi=true'); + } + if (shouldBuildPluginAsAar) { + // Pass a system flag instead of a project flag, so this flag can be + // read from include_flutter.groovy. + command.add('-Dbuild-plugins-as-aars=true'); + // 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; + String detectedGradleErrorLine; + String consumeLog(String line) { + // This message was removed from first-party plugins, + // but older plugin versions still display this message. + if (androidXPluginWarningRegex.hasMatch(line)) { + // Don't pipe. + return null; + } + if (detectedGradleError != null) { + // Pipe stdout/stderr from Gradle. + return line; + } + for (final GradleHandledError gradleError in localGradleErrors) { + if (gradleError.test(line)) { + detectedGradleErrorLine = line; + detectedGradleError = gradleError; + // The first error match wins. + break; + } + } // Pipe stdout/stderr from Gradle. return line; } - for (final GradleHandledError gradleError in localGradleErrors) { - if (gradleError.test(line)) { - detectedGradleErrorLine = line; - detectedGradleError = gradleError; - // The first error match wins. - break; + + final Stopwatch sw = Stopwatch() + ..start(); + int exitCode = 1; + try { + exitCode = await globals.processUtils.stream( + command, + workingDirectory: project.android.hostAppGradleRoot.path, + allowReentrantFlutter: true, + environment: gradleEnvironment, + mapFunction: consumeLog, + ); + } on ProcessException catch (exception) { + consumeLog(exception.toString()); + // Rethrow the exception if the error isn't handled by any of the + // `localGradleErrors`. + if (detectedGradleError == null) { + rethrow; } + } finally { + status.stop(); } - // Pipe stdout/stderr from Gradle. - return line; - } - final Stopwatch sw = Stopwatch()..start(); - int exitCode = 1; - try { - exitCode = await globals.processUtils.stream( - command, - workingDirectory: project.android.hostAppGradleRoot.path, - allowReentrantFlutter: true, - environment: gradleEnvironment, - mapFunction: consumeLog, - ); - } on ProcessException catch (exception) { - consumeLog(exception.toString()); - // Rethrow the exception if the error isn't handled by any of the - // `localGradleErrors`. - if (detectedGradleError == null) { - rethrow; - } - } finally { - status.stop(); - } + globals.flutterUsage.sendTiming('build', 'gradle', sw.elapsed); - globals.flutterUsage.sendTiming('build', 'gradle', sw.elapsed); + if (exitCode != 0) { + if (detectedGradleError == null) { + BuildEvent('gradle-unknown-failure', flutterUsage: globals.flutterUsage).send(); + throwToolExit( + 'Gradle task $assembleTask failed with exit code $exitCode', + exitCode: exitCode, + ); + } else { + final GradleBuildStatus status = await detectedGradleError.handler( + line: detectedGradleErrorLine, + project: project, + usesAndroidX: usesAndroidX, + shouldBuildPluginAsAar: shouldBuildPluginAsAar, + ); - if (exitCode != 0) { - if (detectedGradleError == null) { - BuildEvent('gradle-unknown-failure', flutterUsage: globals.flutterUsage).send(); - throwToolExit( - 'Gradle task $assembleTask failed with exit code $exitCode', - exitCode: exitCode, - ); - } else { - final GradleBuildStatus status = await detectedGradleError.handler( - line: detectedGradleErrorLine, - project: project, - usesAndroidX: usesAndroidX, - shouldBuildPluginAsAar: shouldBuildPluginAsAar, - ); - - if (retries >= 1) { - final String successEventLabel = 'gradle-${detectedGradleError.eventLabel}-success'; - switch (status) { - case GradleBuildStatus.retry: - await buildGradleApp( - project: project, - androidBuildInfo: androidBuildInfo, - target: target, - isBuildingBundle: isBuildingBundle, - localGradleErrors: localGradleErrors, - shouldBuildPluginAsAar: shouldBuildPluginAsAar, - retries: retries - 1, - ); - BuildEvent(successEventLabel, flutterUsage: globals.flutterUsage).send(); - return; - case GradleBuildStatus.retryWithAarPlugins: - await buildGradleApp( - project: project, - androidBuildInfo: androidBuildInfo, - target: target, - isBuildingBundle: isBuildingBundle, - localGradleErrors: localGradleErrors, - shouldBuildPluginAsAar: true, - retries: retries - 1, - ); - BuildEvent(successEventLabel, flutterUsage: globals.flutterUsage).send(); - return; - case GradleBuildStatus.exit: + if (retries >= 1) { + final String successEventLabel = 'gradle-${detectedGradleError.eventLabel}-success'; + switch (status) { + case GradleBuildStatus.retry: + await buildGradleApp( + project: project, + androidBuildInfo: androidBuildInfo, + target: target, + isBuildingBundle: isBuildingBundle, + localGradleErrors: localGradleErrors, + shouldBuildPluginAsAar: shouldBuildPluginAsAar, + retries: retries - 1, + ); + BuildEvent(successEventLabel, flutterUsage: globals.flutterUsage).send(); + return; + case GradleBuildStatus.retryWithAarPlugins: + await buildGradleApp( + project: project, + androidBuildInfo: androidBuildInfo, + target: target, + isBuildingBundle: isBuildingBundle, + localGradleErrors: localGradleErrors, + shouldBuildPluginAsAar: true, + retries: retries - 1, + ); + BuildEvent(successEventLabel, flutterUsage: globals.flutterUsage).send(); + return; + case GradleBuildStatus.exit: // noop. + } } + BuildEvent('gradle-${detectedGradleError.eventLabel}-failure', flutterUsage: globals.flutterUsage).send(); + throwToolExit( + 'Gradle task $assembleTask failed with exit code $exitCode', + exitCode: exitCode, + ); } - BuildEvent('gradle-${detectedGradleError.eventLabel}-failure', flutterUsage: globals.flutterUsage).send(); - throwToolExit( - 'Gradle task $assembleTask failed with exit code $exitCode', - exitCode: exitCode, + } + + if (isBuildingBundle) { + final File bundleFile = findBundleFile(project, buildInfo); + final String appSize = (buildInfo.mode == BuildMode.debug) + ? '' // Don't display the size when building a debug variant. + : ' (${getSizeAsMB(bundleFile.lengthSync())})'; + + if (buildInfo.codeSizeDirectory != null) { + await _performCodeSizeAnalysis('aab', bundleFile, androidBuildInfo); + } + + globals.printStatus( + '$successMark Built ${globals.fs.path.relative(bundleFile.path)}$appSize.', + color: TerminalColor.green, + ); + return; + } + // Gradle produced an APK. + final Iterable apkFilesPaths = project.isModule + ? findApkFilesModule(project, androidBuildInfo) + : listApkPaths(androidBuildInfo); + final Directory apkDirectory = getApkDirectory(project); + final File apkFile = apkDirectory.childFile(apkFilesPaths.first); + if (!apkFile.existsSync()) { + _exitWithExpectedFileNotFound( + project: project, + fileExtension: '.apk', ); } - } - if (isBuildingBundle) { - final File bundleFile = findBundleFile(project, buildInfo); + // Copy the first APK to app.apk, so `flutter run` can find it. + // TODO(egarciad): Handle multiple APKs. + apkFile.copySync(apkDirectory + .childFile('app.apk') + .path); + globals.printTrace('calculateSha: $apkDirectory/app.apk'); + + final File apkShaFile = apkDirectory.childFile('app.apk.sha1'); + apkShaFile.writeAsStringSync(_calculateSha(apkFile)); + final String appSize = (buildInfo.mode == BuildMode.debug) - ? '' // Don't display the size when building a debug variant. - : ' (${getSizeAsMB(bundleFile.lengthSync())})'; - - if (buildInfo.codeSizeDirectory != null) { - await _performCodeSizeAnalysis('aab', bundleFile, androidBuildInfo); - } - + ? '' // Don't display the size when building a debug variant. + : ' (${getSizeAsMB(apkFile.lengthSync())})'; globals.printStatus( - '$successMark Built ${globals.fs.path.relative(bundleFile.path)}$appSize.', + '$successMark Built ${globals.fs.path.relative(apkFile.path)}$appSize.', color: TerminalColor.green, ); - return; + + if (buildInfo.codeSizeDirectory != null) { + await _performCodeSizeAnalysis('apk', apkFile, androidBuildInfo); + } } - // Gradle produced an APK. - final Iterable apkFilesPaths = project.isModule - ? findApkFilesModule(project, androidBuildInfo) - : listApkPaths(androidBuildInfo); - final Directory apkDirectory = getApkDirectory(project); - final File apkFile = apkDirectory.childFile(apkFilesPaths.first); - if (!apkFile.existsSync()) { - _exitWithExpectedFileNotFound( - project: project, - fileExtension: '.apk', + + Future _performCodeSizeAnalysis(String kind, + File zipFile, + AndroidBuildInfo androidBuildInfo,) async { + final SizeAnalyzer sizeAnalyzer = SizeAnalyzer( + fileSystem: globals.fs, + logger: globals.logger, + flutterUsage: globals.flutterUsage, ); - } - - // Copy the first APK to app.apk, so `flutter run` can find it. - // TODO(egarciad): Handle multiple APKs. - apkFile.copySync(apkDirectory.childFile('app.apk').path); - globals.printTrace('calculateSha: $apkDirectory/app.apk'); - - final File apkShaFile = apkDirectory.childFile('app.apk.sha1'); - apkShaFile.writeAsStringSync(_calculateSha(apkFile)); - - final String appSize = (buildInfo.mode == BuildMode.debug) - ? '' // Don't display the size when building a debug variant. - : ' (${getSizeAsMB(apkFile.lengthSync())})'; - globals.printStatus( - '$successMark Built ${globals.fs.path.relative(apkFile.path)}$appSize.', - color: TerminalColor.green, - ); - - if (buildInfo.codeSizeDirectory != null) { - await _performCodeSizeAnalysis('apk', apkFile, androidBuildInfo); - } -} - -Future _performCodeSizeAnalysis( - String kind, - File zipFile, - AndroidBuildInfo androidBuildInfo, -) async { - final SizeAnalyzer sizeAnalyzer = SizeAnalyzer( - fileSystem: globals.fs, - logger: globals.logger, - flutterUsage: globals.flutterUsage, - ); - final String archName = getNameForAndroidArch(androidBuildInfo.targetArchs.single); - final BuildInfo buildInfo = androidBuildInfo.buildInfo; - final File aotSnapshot = globals.fs.directory(buildInfo.codeSizeDirectory) - .childFile('snapshot.$archName.json'); - final File precompilerTrace = globals.fs.directory(buildInfo.codeSizeDirectory) - .childFile('trace.$archName.json'); - final Map output = await sizeAnalyzer.analyzeZipSizeAndAotSnapshot( - zipFile: zipFile, - aotSnapshot: aotSnapshot, - precompilerTrace: precompilerTrace, - kind: kind, - ); - final File outputFile = globals.fsUtils.getUniqueFile( - globals.fs - .directory(globals.fsUtils.homeDirPath) - .childDirectory('.flutter-devtools'), '$kind-code-size-analysis', 'json', - )..writeAsStringSync(jsonEncode(output)); - // This message is used as a sentinel in analyze_apk_size_test.dart - globals.printStatus( - 'A summary of your ${kind.toUpperCase()} analysis can be found at: ${outputFile.path}', - ); - - // DevTools expects a file path relative to the .flutter-devtools/ dir. - final String relativeAppSizePath = outputFile.path.split('.flutter-devtools/').last.trim(); - globals.printStatus( - '\nTo analyze your app size in Dart DevTools, run the following command:\n' - 'flutter pub global activate devtools; flutter pub global run devtools ' - '--appSizeBase=$relativeAppSizePath' - ); -} - -/// Builds AAR and POM files. -/// -/// * [project] is typically [FlutterProject.current()]. -/// * [androidBuildInfo] is the build configuration. -/// * [outputDir] is the destination of the artifacts, -/// * [buildNumber] is the build number of the output aar, -Future buildGradleAar({ - @required FlutterProject project, - @required AndroidBuildInfo androidBuildInfo, - @required String target, - @required Directory outputDirectory, - @required String buildNumber, -}) async { - assert(project != null); - assert(target != null); - assert(androidBuildInfo != null); - assert(outputDirectory != null); - assert(globals.androidSdk != null); - - final FlutterManifest manifest = project.manifest; - if (!manifest.isModule && !manifest.isPlugin) { - throwToolExit('AARs can only be built for plugin or module projects.'); - } - - final BuildInfo buildInfo = androidBuildInfo.buildInfo; - final String aarTask = getAarTaskFor(buildInfo); - final Status status = globals.logger.startProgress( - "Running Gradle task '$aarTask'...", - multilineOutput: true, - ); - - final String flutterRoot = globals.fs.path.absolute(Cache.flutterRoot); - final String initScript = globals.fs.path.join( - flutterRoot, - 'packages', - 'flutter_tools', - 'gradle', - 'aar_init_script.gradle', - ); - final List command = [ - gradleUtils.getExecutable(project), - '-I=$initScript', - '-Pflutter-root=$flutterRoot', - '-Poutput-dir=${outputDirectory.path}', - '-Pis-plugin=${manifest.isPlugin}', - '-PbuildNumber=$buildNumber' - ]; - if (globals.logger.isVerbose) { - command.add('-Pverbose=true'); - } else { - command.add('-q'); - } - if (!buildInfo.androidGradleDaemon) { - command.add('--no-daemon'); - } - - if (target != null && target.isNotEmpty) { - command.add('-Ptarget=$target'); - } - command.addAll(androidBuildInfo.buildInfo.toGradleConfig()); - if (buildInfo.dartObfuscation && buildInfo.mode != BuildMode.release) { + final String archName = getNameForAndroidArch(androidBuildInfo.targetArchs.single); + final BuildInfo buildInfo = androidBuildInfo.buildInfo; + final File aotSnapshot = globals.fs.directory(buildInfo.codeSizeDirectory) + .childFile('snapshot.$archName.json'); + final File precompilerTrace = globals.fs.directory(buildInfo.codeSizeDirectory) + .childFile('trace.$archName.json'); + final Map output = await sizeAnalyzer.analyzeZipSizeAndAotSnapshot( + zipFile: zipFile, + aotSnapshot: aotSnapshot, + precompilerTrace: precompilerTrace, + kind: kind, + ); + final File outputFile = globals.fsUtils.getUniqueFile( + globals.fs + .directory(globals.fsUtils.homeDirPath) + .childDirectory('.flutter-devtools'), '$kind-code-size-analysis', 'json', + ) + ..writeAsStringSync(jsonEncode(output)); + // This message is used as a sentinel in analyze_apk_size_test.dart globals.printStatus( - 'Dart obfuscation is not supported in ${toTitleCase(buildInfo.friendlyModeName)}' - ' mode, building as un-obfuscated.', + 'A summary of your ${kind.toUpperCase()} analysis can be found at: ${outputFile.path}', + ); + + // DevTools expects a file path relative to the .flutter-devtools/ dir. + final String relativeAppSizePath = outputFile.path + .split('.flutter-devtools/') + .last + .trim(); + globals.printStatus( + '\nTo analyze your app size in Dart DevTools, run the following command:\n' + 'flutter pub global activate devtools; flutter pub global run devtools ' + '--appSizeBase=$relativeAppSizePath' ); } - if (globals.artifacts is LocalEngineArtifacts) { - final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts; - final Directory localEngineRepo = _getLocalEngineRepo( - engineOutPath: localEngineArtifacts.engineOutPath, - androidBuildInfo: androidBuildInfo, - ); - globals.printTrace( - 'Using local engine: ${localEngineArtifacts.engineOutPath}\n' - 'Local Maven repo: ${localEngineRepo.path}' - ); - command.add('-Plocal-engine-repo=${localEngineRepo.path}'); - command.add('-Plocal-engine-build-mode=${buildInfo.modeName}'); - command.add('-Plocal-engine-out=${localEngineArtifacts.engineOutPath}'); + /// Builds AAR and POM files. + /// + /// * [project] is typically [FlutterProject.current()]. + /// * [androidBuildInfo] is the build configuration. + /// * [outputDir] is the destination of the artifacts, + /// * [buildNumber] is the build number of the output aar, + Future buildGradleAar({ + @required FlutterProject project, + @required AndroidBuildInfo androidBuildInfo, + @required String target, + @required Directory outputDirectory, + @required String buildNumber, + }) async { + assert(project != null); + assert(target != null); + assert(androidBuildInfo != null); + assert(outputDirectory != null); + assert(globals.androidSdk != null); - // Copy the local engine repo in the output directory. - try { - globals.fsUtils.copyDirectorySync( - localEngineRepo, - getRepoDirectory(outputDirectory), - ); - } on FileSystemException catch(_) { - throwToolExit( - 'Failed to copy the local engine ${localEngineRepo.path} repo ' - 'in ${outputDirectory.path}' + final FlutterManifest manifest = project.manifest; + if (!manifest.isModule && !manifest.isPlugin) { + throwToolExit('AARs can only be built for plugin or module projects.'); + } + + final BuildInfo buildInfo = androidBuildInfo.buildInfo; + final String aarTask = getAarTaskFor(buildInfo); + final Status status = globals.logger.startProgress( + "Running Gradle task '$aarTask'...", + multilineOutput: true, + ); + + final String flutterRoot = globals.fs.path.absolute(Cache.flutterRoot); + final String initScript = globals.fs.path.join( + flutterRoot, + 'packages', + 'flutter_tools', + 'gradle', + 'aar_init_script.gradle', + ); + final List command = [ + gradleUtils.getExecutable(project), + '-I=$initScript', + '-Pflutter-root=$flutterRoot', + '-Poutput-dir=${outputDirectory.path}', + '-Pis-plugin=${manifest.isPlugin}', + '-PbuildNumber=$buildNumber' + ]; + if (globals.logger.isVerbose) { + command.add('-Pverbose=true'); + } else { + command.add('-q'); + } + if (!buildInfo.androidGradleDaemon) { + command.add('--no-daemon'); + } + + if (target != null && target.isNotEmpty) { + command.add('-Ptarget=$target'); + } + command.addAll(androidBuildInfo.buildInfo.toGradleConfig()); + if (buildInfo.dartObfuscation && buildInfo.mode != BuildMode.release) { + globals.printStatus( + 'Dart obfuscation is not supported in ${toTitleCase(buildInfo.friendlyModeName)}' + ' mode, building as un-obfuscated.', ); } - command.add('-Ptarget-platform=${_getTargetPlatformByLocalEnginePath( - localEngineArtifacts.engineOutPath)}'); - } else if (androidBuildInfo.targetArchs.isNotEmpty) { - final String targetPlatforms = androidBuildInfo.targetArchs - .map(getPlatformNameForAndroidArch).join(','); - command.add('-Ptarget-platform=$targetPlatforms'); - } - command.add(aarTask); + if (globals.artifacts is LocalEngineArtifacts) { + final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts; + final Directory localEngineRepo = _getLocalEngineRepo( + engineOutPath: localEngineArtifacts.engineOutPath, + androidBuildInfo: androidBuildInfo, + ); + globals.printTrace( + 'Using local engine: ${localEngineArtifacts.engineOutPath}\n' + 'Local Maven repo: ${localEngineRepo.path}' + ); + command.add('-Plocal-engine-repo=${localEngineRepo.path}'); + command.add('-Plocal-engine-build-mode=${buildInfo.modeName}'); + command.add('-Plocal-engine-out=${localEngineArtifacts.engineOutPath}'); - final Stopwatch sw = Stopwatch()..start(); - RunResult result; - try { - result = await globals.processUtils.run( - command, - workingDirectory: project.android.hostAppGradleRoot.path, - allowReentrantFlutter: true, - environment: gradleEnvironment, - ); - } finally { - status.stop(); - } - globals.flutterUsage.sendTiming('build', 'gradle-aar', sw.elapsed); + // Copy the local engine repo in the output directory. + try { + globals.fsUtils.copyDirectorySync( + localEngineRepo, + getRepoDirectory(outputDirectory), + ); + } on FileSystemException catch (_) { + throwToolExit( + 'Failed to copy the local engine ${localEngineRepo.path} repo ' + 'in ${outputDirectory.path}' + ); + } + command.add('-Ptarget-platform=${_getTargetPlatformByLocalEnginePath( + localEngineArtifacts.engineOutPath)}'); + } else if (androidBuildInfo.targetArchs.isNotEmpty) { + final String targetPlatforms = androidBuildInfo.targetArchs + .map(getPlatformNameForAndroidArch).join(','); + command.add('-Ptarget-platform=$targetPlatforms'); + } - if (result.exitCode != 0) { - globals.printStatus(result.stdout, wrap: false); - globals.printError(result.stderr, wrap: false); - throwToolExit( - 'Gradle task $aarTask failed with exit code ${result.exitCode}.', - exitCode: result.exitCode, + command.add(aarTask); + + final Stopwatch sw = Stopwatch() + ..start(); + RunResult result; + try { + result = await globals.processUtils.run( + command, + workingDirectory: project.android.hostAppGradleRoot.path, + allowReentrantFlutter: true, + environment: gradleEnvironment, + ); + } finally { + status.stop(); + } + globals.flutterUsage.sendTiming('build', 'gradle-aar', sw.elapsed); + + if (result.exitCode != 0) { + globals.printStatus(result.stdout, wrap: false); + globals.printError(result.stderr, wrap: false); + throwToolExit( + 'Gradle task $aarTask failed with exit code ${result.exitCode}.', + exitCode: result.exitCode, + ); + } + final Directory repoDirectory = getRepoDirectory(outputDirectory); + if (!repoDirectory.existsSync()) { + globals.printStatus(result.stdout, wrap: false); + globals.printError(result.stderr, wrap: false); + throwToolExit( + 'Gradle task $aarTask failed to produce $repoDirectory.', + exitCode: exitCode, + ); + } + globals.printStatus( + '$successMark Built ${globals.fs.path.relative(repoDirectory.path)}.', + color: TerminalColor.green, ); } - final Directory repoDirectory = getRepoDirectory(outputDirectory); - if (!repoDirectory.existsSync()) { - globals.printStatus(result.stdout, wrap: false); - globals.printError(result.stderr, wrap: false); - throwToolExit( - 'Gradle task $aarTask failed to produce $repoDirectory.', - exitCode: exitCode, - ); + + /// Builds the plugins as AARs. + @visibleForTesting + Future buildPluginsAsAar( + FlutterProject flutterProject, + AndroidBuildInfo androidBuildInfo, { + Directory buildDirectory, + }) async { + final File flutterPluginFile = flutterProject.flutterPluginsFile; + if (!flutterPluginFile.existsSync()) { + return; + } + final List plugins = flutterPluginFile.readAsStringSync().split('\n'); + for (final String plugin in plugins) { + final List pluginParts = plugin.split('='); + if (pluginParts.length != 2) { + continue; + } + final Directory pluginDirectory = globals.fs.directory(pluginParts.last); + assert(pluginDirectory.existsSync()); + + final String pluginName = pluginParts.first; + final File buildGradleFile = pluginDirectory.childDirectory('android').childFile('build.gradle'); + if (!buildGradleFile.existsSync()) { + globals.printTrace("Skipping plugin $pluginName since it doesn't have a android/build.gradle file"); + continue; + } + globals.logger.printStatus('Building plugin $pluginName...'); + try { + await buildGradleAar( + project: FlutterProject.fromDirectory(pluginDirectory), + androidBuildInfo: AndroidBuildInfo( + BuildInfo( + BuildMode.release, // Plugins are built as release. + null, // Plugins don't define flavors. + treeShakeIcons: androidBuildInfo.buildInfo.treeShakeIcons, + packagesPath: androidBuildInfo.buildInfo.packagesPath, + ), + ), + target: '', + outputDirectory: buildDirectory, + buildNumber: '1.0' + ); + } on ToolExit { + // Log the entire plugin entry in `.flutter-plugins` since it + // includes the plugin name and the version. + BuildEvent('gradle-plugin-aar-failure', eventError: plugin, flutterUsage: globals.flutterUsage).send(); + throwToolExit('The plugin $pluginName could not be built due to the issue above.'); + } + } } - globals.printStatus( - '$successMark Built ${globals.fs.path.relative(repoDirectory.path)}.', - color: TerminalColor.green, - ); + } /// Prints how to consume the AAR from a host app. @@ -774,57 +917,6 @@ bool isAppUsingAndroidX(Directory androidDirectory) { return properties.readAsStringSync().contains('android.useAndroidX=true'); } -/// Builds the plugins as AARs. -@visibleForTesting -Future buildPluginsAsAar( - FlutterProject flutterProject, - AndroidBuildInfo androidBuildInfo, { - Directory buildDirectory, -}) async { - final File flutterPluginFile = flutterProject.flutterPluginsFile; - if (!flutterPluginFile.existsSync()) { - return; - } - final List plugins = flutterPluginFile.readAsStringSync().split('\n'); - for (final String plugin in plugins) { - final List pluginParts = plugin.split('='); - if (pluginParts.length != 2) { - continue; - } - final Directory pluginDirectory = globals.fs.directory(pluginParts.last); - assert(pluginDirectory.existsSync()); - - final String pluginName = pluginParts.first; - final File buildGradleFile = pluginDirectory.childDirectory('android').childFile('build.gradle'); - if (!buildGradleFile.existsSync()) { - globals.printTrace("Skipping plugin $pluginName since it doesn't have a android/build.gradle file"); - continue; - } - globals.logger.printStatus('Building plugin $pluginName...'); - try { - await buildGradleAar( - project: FlutterProject.fromDirectory(pluginDirectory), - androidBuildInfo: AndroidBuildInfo( - BuildInfo( - BuildMode.release, // Plugins are built as release. - null, // Plugins don't define flavors. - treeShakeIcons: androidBuildInfo.buildInfo.treeShakeIcons, - packagesPath: androidBuildInfo.buildInfo.packagesPath, - ), - ), - target: '', - outputDirectory: buildDirectory, - buildNumber: '1.0' - ); - } on ToolExit { - // Log the entire plugin entry in `.flutter-plugins` since it - // includes the plugin name and the version. - BuildEvent('gradle-plugin-aar-failure', eventError: plugin, flutterUsage: globals.flutterUsage).send(); - throwToolExit('The plugin $pluginName could not be built due to the issue above.'); - } - } -} - /// Returns the APK files for a given [FlutterProject] and [AndroidBuildInfo]. @visibleForTesting Iterable findApkFilesModule( diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index ae436f5611..2625c16df1 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -8,9 +8,11 @@ import 'dart:async'; import 'package:process/process.dart'; +import 'android/android_builder.dart'; import 'android/android_sdk.dart'; import 'android/android_studio.dart'; import 'android/android_workflow.dart'; +import 'android/gradle.dart'; import 'android/gradle_utils.dart'; import 'application_package.dart'; import 'artifacts.dart'; @@ -77,6 +79,7 @@ Future runInContext( body: runnerWrapper, overrides: overrides, fallbacks: { + AndroidBuilder: () => AndroidGradleBuilder(), AndroidLicenseValidator: () => AndroidLicenseValidator( operatingSystemUtils: globals.os, platform: globals.platform, diff --git a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart new file mode 100644 index 0000000000..02b8bc1252 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart @@ -0,0 +1,1652 @@ +// 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. + +// @dart = 2.8 + +import 'package:archive/archive.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/android/android_sdk.dart'; +import 'package:flutter_tools/src/android/android_studio.dart'; +import 'package:flutter_tools/src/android/gradle.dart'; +import 'package:flutter_tools/src/android/gradle_errors.dart'; +import 'package:flutter_tools/src/artifacts.dart'; +import 'package:flutter_tools/src/base/context.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/reporting/reporting.dart'; +import 'package:mockito/mockito.dart'; +import 'package:process/process.dart'; + +import '../../src/common.dart'; +import '../../src/context.dart'; +import '../../src/mocks.dart'; + +void main() { + group('gradle build', () { + TestUsage testUsage; + MockAndroidSdk mockAndroidSdk; + MockAndroidStudio mockAndroidStudio; + MockLocalEngineArtifacts mockArtifacts; + MockProcessManager mockProcessManager; + FakePlatform android; + FileSystem fileSystem; + FileSystemUtils fileSystemUtils; + Cache cache; + AndroidGradleBuilder builder; + + setUp(() { + testUsage = TestUsage(); + fileSystem = MemoryFileSystem.test(); + fileSystemUtils = MockFileSystemUtils(); + mockAndroidSdk = MockAndroidSdk(); + mockAndroidStudio = MockAndroidStudio(); + mockArtifacts = MockLocalEngineArtifacts(); + mockProcessManager = MockProcessManager(); + android = fakePlatform('android'); + builder = AndroidGradleBuilder(); + + when(mockAndroidSdk.directory).thenReturn('irrelevant'); + + final Directory rootDirectory = fileSystem.currentDirectory; + cache = Cache.test( + rootOverride: rootDirectory, + fileSystem: fileSystem, + ); + + final Directory gradleWrapperDirectory = rootDirectory + .childDirectory('bin') + .childDirectory('cache') + .childDirectory('artifacts') + .childDirectory('gradle_wrapper'); + gradleWrapperDirectory.createSync(recursive: true); + gradleWrapperDirectory + .childFile('gradlew') + .writeAsStringSync('irrelevant'); + gradleWrapperDirectory + .childDirectory('gradle') + .childDirectory('wrapper') + .createSync(recursive: true); + gradleWrapperDirectory + .childDirectory('gradle') + .childDirectory('wrapper') + .childFile('gradle-wrapper.jar') + .writeAsStringSync('irrelevant'); + }); + + testUsingContext('recognizes common errors - tool exit', () async { + final Process process = createMockProcess( + exitCode: 1, + stdout: 'irrelevant\nSome gradle message\nirrelevant', + ); + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) => Future.value(process)); + + fileSystem.directory('android') + .childFile('build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + bool handlerCalled = false; + await expectLater(() async { + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: [ + GradleHandledError( + test: (String line) { + return line.contains('Some gradle message'); + }, + handler: ({ + String line, + FlutterProject project, + bool usesAndroidX, + bool shouldBuildPluginAsAar, + }) async { + handlerCalled = true; + return GradleBuildStatus.exit; + }, + eventLabel: 'random-event-label', + ), + ], + ); + }, + throwsToolExit( + message: 'Gradle task assembleRelease failed with exit code 1' + )); + + expect(handlerCalled, isTrue); + + expect(testUsage.events, contains( + const TestUsageEvent( + 'build', + 'unspecified', + label: 'gradle-random-event-label-failure', + parameters: {}, + ), + )); + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + ProcessManager: () => mockProcessManager, + Usage: () => testUsage, + }); + + testUsingContext('recognizes common errors - retry build', () async { + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) { + final Process process = createMockProcess( + exitCode: 1, + stdout: 'irrelevant\nSome gradle message\nirrelevant', + ); + return Future.value(process); + }); + + fileSystem.directory('android') + .childFile('build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + int testFnCalled = 0; + await expectLater(() async { + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: [ + GradleHandledError( + test: (String line) { + if (line.contains('Some gradle message')) { + testFnCalled++; + return true; + } + return false; + }, + handler: ({ + String line, + FlutterProject project, + bool usesAndroidX, + bool shouldBuildPluginAsAar, + }) async { + return GradleBuildStatus.retry; + }, + eventLabel: 'random-event-label', + ), + ], + ); + }, throwsToolExit( + message: 'Gradle task assembleRelease failed with exit code 1' + )); + + expect(testFnCalled, equals(2)); + expect(testUsage.events, contains( + const TestUsageEvent( + 'build', + 'unspecified', + label: 'gradle-random-event-label-failure', + parameters: {}, + ), + )); + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + ProcessManager: () => mockProcessManager, + Usage: () => testUsage, + }); + + testUsingContext('recognizes process exceptions - tool exit', () async { + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenThrow(const ProcessException('', [], 'Some gradle message')); + + fileSystem.directory('android') + .childFile('build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + bool handlerCalled = false; + await expectLater(() async { + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: [ + GradleHandledError( + test: (String line) { + return line.contains('Some gradle message'); + }, + handler: ({ + String line, + FlutterProject project, + bool usesAndroidX, + bool shouldBuildPluginAsAar, + }) async { + handlerCalled = true; + return GradleBuildStatus.exit; + }, + eventLabel: 'random-event-label', + ), + ], + ); + }, + throwsToolExit( + message: 'Gradle task assembleRelease failed with exit code 1' + )); + + expect(handlerCalled, isTrue); + + expect(testUsage.events, contains( + const TestUsageEvent( + 'build', + 'unspecified', + label: 'gradle-random-event-label-failure', + parameters: {}, + ), + )); + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + ProcessManager: () => mockProcessManager, + Usage: () => testUsage, + }); + + testUsingContext('rethrows unrecognized ProcessException', () async { + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenThrow(const ProcessException('', [], 'Unrecognized')); + + fileSystem.directory('android') + .childFile('build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + await expectLater(() async { + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: const [], + ); + }, + throwsA(isA())); + + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + ProcessManager: () => mockProcessManager, + }); + + testUsingContext('logs success event after a successful retry', () async { + int testFnCalled = 0; + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) { + Process process; + if (testFnCalled == 0) { + process = createMockProcess( + exitCode: 1, + stdout: 'irrelevant\nSome gradle message\nirrelevant', + ); + } else { + process = createMockProcess( + exitCode: 0, + stdout: 'irrelevant', + ); + } + testFnCalled++; + return Future.value(process); + }); + + fileSystem.directory('android') + .childFile('build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + fileSystem.directory('build') + .childDirectory('app') + .childDirectory('outputs') + .childDirectory('flutter-apk') + .childFile('app-release.apk') + .createSync(recursive: true); + + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: [ + GradleHandledError( + test: (String line) { + return line.contains('Some gradle message'); + }, + handler: ({ + String line, + FlutterProject project, + bool usesAndroidX, + bool shouldBuildPluginAsAar, + }) async { + return GradleBuildStatus.retry; + }, + eventLabel: 'random-event-label', + ), + ], + ); + + expect(testUsage.events, contains( + const TestUsageEvent( + 'build', + 'unspecified', + label: 'gradle-random-event-label-success', + parameters: {}, + ), + )); + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + Cache: () => cache, + FileSystem: () => fileSystem, + Platform: () => android, + ProcessManager: () => mockProcessManager, + Usage: () => testUsage, + }); + + testUsingContext('performs code size analysis and sends analytics', () async { + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) { + return Future.value(createMockProcess( + exitCode: 0, + stdout: 'irrelevant', + )); + }); + + fileSystem.directory('android') + .childFile('build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + final Archive archive = Archive() + ..addFile(ArchiveFile('AndroidManifest.xml', 100, List.filled(100, 0))) + ..addFile(ArchiveFile('META-INF/CERT.RSA', 10, List.filled(10, 0))) + ..addFile(ArchiveFile('META-INF/CERT.SF', 10, List.filled(10, 0))) + ..addFile(ArchiveFile('lib/arm64-v8a/libapp.so', 50, List.filled(50, 0))) + ..addFile(ArchiveFile('lib/arm64-v8a/libflutter.so', 50, List.filled(50, 0))); + + fileSystem.directory('build') + .childDirectory('app') + .childDirectory('outputs') + .childDirectory('flutter-apk') + .childFile('app-release.apk') + ..createSync(recursive: true) + ..writeAsBytesSync(ZipEncoder().encode(archive)); + + fileSystem.file('foo/snapshot.arm64-v8a.json') + ..createSync(recursive: true) + ..writeAsStringSync(r''' +[ + { + "l": "dart:_internal", + "c": "SubListIterable", + "n": "[Optimized] skip", + "s": 2400 + } +]'''); + fileSystem.file('foo/trace.arm64-v8a.json') + ..createSync(recursive: true) + ..writeAsStringSync('{}'); + + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + codeSizeDirectory: 'foo', + ), + targetArchs: [AndroidArch.arm64_v8a], + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: [], + ); + + expect(testUsage.events, contains( + const TestUsageEvent( + 'code-size-analysis', + 'apk', + ), + )); + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + Cache: () => cache, + FileSystem: () => fileSystem, + Platform: () => android, + ProcessManager: () => mockProcessManager, + Usage: () => testUsage, + }); + + testUsingContext('recognizes common errors - retry build with AAR plugins', () async { + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) { + final Process process = createMockProcess( + exitCode: 1, + stdout: 'irrelevant\nSome gradle message\nirrelevant', + ); + return Future.value(process); + }); + + fileSystem.directory('android') + .childFile('build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + int testFnCalled = 0; + bool builtPluginAsAar = false; + await expectLater(() async { + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: [ + GradleHandledError( + test: (String line) { + if (line.contains('Some gradle message')) { + testFnCalled++; + return true; + } + return false; + }, + handler: ({ + String line, + FlutterProject project, + bool usesAndroidX, + bool shouldBuildPluginAsAar, + }) async { + if (testFnCalled == 2) { + builtPluginAsAar = shouldBuildPluginAsAar; + } + return GradleBuildStatus.retryWithAarPlugins; + }, + eventLabel: 'random-event-label', + ), + ], + ); + }, throwsToolExit( + message: 'Gradle task assembleRelease failed with exit code 1' + )); + + expect(testFnCalled, equals(2)); + expect(builtPluginAsAar, isTrue); + + expect(testUsage.events, contains( + const TestUsageEvent( + 'build', + 'unspecified', + label: 'gradle-random-event-label-failure', + parameters: {}, + ), + )); + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + ProcessManager: () => mockProcessManager, + Usage: () => testUsage, + }); + + testUsingContext('indicates that an APK has been built successfully', () async { + fileSystem.directory('android') + .childFile('build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + fileSystem.directory('build') + .childDirectory('app') + .childDirectory('outputs') + .childDirectory('flutter-apk') + .childFile('app-release.apk') + .createSync(recursive: true); + + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: const [], + ); + + expect( + testLogger.statusText, + contains('Built build/app/outputs/flutter-apk/app-release.apk (0.0MB)'), + ); + + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + Cache: () => cache, + FileSystem: () => fileSystem, + Platform: () => android, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext("doesn't indicate how to consume an AAR when printHowToConsumeAaar is false", () async { + final File manifestFile = fileSystem.file('pubspec.yaml'); + manifestFile.createSync(recursive: true); + manifestFile.writeAsStringSync(''' + flutter: + module: + androidPackage: com.example.test + ''' + ); + + fileSystem.file('.android/gradlew').createSync(recursive: true); + + fileSystem.file('.android/gradle.properties') + .writeAsStringSync('irrelevant'); + + fileSystem.file('.android/build.gradle') + .createSync(recursive: true); + + // Let any process start. Assert after. + when(mockProcessManager.run( + any, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); + + fileSystem.directory('build/outputs/repo').createSync(recursive: true); + + await builder.buildGradleAar( + androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)), + project: FlutterProject.current(), + outputDirectory: fileSystem.directory('build/'), + target: '', + buildNumber: '1.0', + ); + + expect( + testLogger.statusText, + contains('Built build/outputs/repo'), + ); + expect( + testLogger.statusText.contains('Consuming the Module'), + isFalse, + ); + + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + AndroidStudio: () => mockAndroidStudio, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + ProcessManager: () => mockProcessManager, + }); + + testUsingContext('gradle exit code is properly passed on', () async { + final File manifestFile = fileSystem.file('pubspec.yaml'); + manifestFile.createSync(recursive: true); + manifestFile.writeAsStringSync(''' + flutter: + module: + androidPackage: com.example.test + ''' + ); + + fileSystem.file('.android/gradlew').createSync(recursive: true); + + fileSystem.file('.android/gradle.properties') + .writeAsStringSync('irrelevant'); + + fileSystem.file('.android/build.gradle') + .createSync(recursive: true); + + // Let any process start. Assert after. + when(mockProcessManager.run( + any, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )).thenAnswer((_) async => ProcessResult(1, 108, '', '')); + + fileSystem.directory('build/outputs/repo').createSync(recursive: true); + + await expectLater(() async => + await builder.buildGradleAar( + androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)), + project: FlutterProject.current(), + outputDirectory: fileSystem.directory('build/'), + target: '', + buildNumber: '1.0', + ) + , throwsToolExit(exitCode: 108, message: 'Gradle task assembleAarRelease failed with exit code 108.')); + + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + AndroidStudio: () => mockAndroidStudio, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + ProcessManager: () => mockProcessManager, + }); + + testUsingContext('build apk uses selected local engine,the engine abi is arm', () async { + when(mockArtifacts.getArtifactPath( + Artifact.flutterFramework, + platform: TargetPlatform.android_arm, + mode: anyNamed('mode'), + environmentType: anyNamed('environmentType'), + )).thenReturn('engine'); + when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm')); + + fileSystem.file('out/android_arm/flutter_embedding_release.pom') + ..createSync(recursive: true) + ..writeAsStringSync(''' + + + 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b + + + +'''); + fileSystem.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true); + fileSystem.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true); + fileSystem.file('out/android_arm/armeabi_v7a_release.maven-metadata.xml').createSync(recursive: true); + fileSystem.file('out/android_arm/flutter_embedding_release.jar').createSync(recursive: true); + fileSystem.file('out/android_arm/flutter_embedding_release.pom').createSync(recursive: true); + fileSystem.file('out/android_arm/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); + + fileSystem.file('android/gradlew').createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.file('android/build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + // Let any process start. Assert after. + when(mockProcessManager.run( + any, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); + + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) { + return Future.value( + createMockProcess( + exitCode: 1, + ) + ); + }); + + await expectLater(() async { + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: const [], + ); + }, throwsToolExit()); + + final List actualGradlewCall = verify( + mockProcessManager.start( + captureAny, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory') + ), + ).captured.last as List; + + expect(actualGradlewCall, contains('/android/gradlew')); + expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm')); + expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); + expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); + + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + AndroidStudio: () => mockAndroidStudio, + Artifacts: () => mockArtifacts, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + ProcessManager: () => mockProcessManager, + }); + + testUsingContext( + 'build apk uses selected local engine,the engine abi is arm64', () async { + when(mockArtifacts.getArtifactPath( + Artifact.flutterFramework, + platform: anyNamed('platform'), + mode: anyNamed('mode'), + environmentType: anyNamed('environmentType'), + )).thenReturn('engine'); + when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm64')); + + fileSystem.file('out/android_arm64/flutter_embedding_release.pom') + ..createSync(recursive: true) + ..writeAsStringSync(''' + + + 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b + + + +'''); + fileSystem.file('out/android_arm64/arm64_v8a_release.pom').createSync(recursive: true); + fileSystem.file('out/android_arm64/arm64_v8a_release.jar').createSync(recursive: true); + fileSystem.file('out/android_arm64/arm64_v8a_release.maven-metadata.xml').createSync(recursive: true); + fileSystem.file('out/android_arm64/flutter_embedding_release.jar').createSync(recursive: true); + fileSystem.file('out/android_arm64/flutter_embedding_release.pom').createSync(recursive: true); + fileSystem.file('out/android_arm64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); + + fileSystem.file('android/gradlew').createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.file('android/build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + // Let any process start. Assert after. + when(mockProcessManager.run( + any, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); + + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) { + return Future.value( + createMockProcess( + exitCode: 1, + ) + ); + }); + + await expectLater(() async { + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: const [], + ); + }, throwsToolExit()); + + final List actualGradlewCall = verify( + mockProcessManager.start( + captureAny, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory') + ), + ).captured.last as List; + + expect(actualGradlewCall, contains('/android/gradlew')); + expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm64')); + expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); + expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); + + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + AndroidStudio: () => mockAndroidStudio, + Artifacts: () => mockArtifacts, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + ProcessManager: () => mockProcessManager, + }); + + testUsingContext( + 'build apk uses selected local engine,the engine abi is x86', () async { + when(mockArtifacts.getArtifactPath( + Artifact.flutterFramework, + platform: anyNamed('platform'), + mode: anyNamed('mode'), + environmentType: anyNamed('environmentType'), + )).thenReturn('engine'); + when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x86')); + + fileSystem.file('out/android_x86/flutter_embedding_release.pom') + ..createSync(recursive: true) + ..writeAsStringSync(''' + + + 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b + + + +'''); + fileSystem.file('out/android_x86/x86_release.pom').createSync(recursive: true); + fileSystem.file('out/android_x86/x86_release.jar').createSync(recursive: true); + fileSystem.file('out/android_x86/x86_release.maven-metadata.xml').createSync(recursive: true); + fileSystem.file('out/android_x86/flutter_embedding_release.jar').createSync(recursive: true); + fileSystem.file('out/android_x86/flutter_embedding_release.pom').createSync(recursive: true); + fileSystem.file('out/android_x86/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); + + fileSystem.file('android/gradlew').createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.file('android/build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + // Let any process start. Assert after. + when(mockProcessManager.run( + any, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); + + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) { + return Future.value( + createMockProcess( + exitCode: 1, + ) + ); + }); + + await expectLater(() async { + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: const [], + ); + }, throwsToolExit()); + + final List actualGradlewCall = verify( + mockProcessManager.start( + captureAny, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory') + ), + ).captured.last as List; + + expect(actualGradlewCall, contains('/android/gradlew')); + expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x86')); + expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); + expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); + + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + AndroidStudio: () => mockAndroidStudio, + Artifacts: () => mockArtifacts, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + ProcessManager: () => mockProcessManager, + }); + + testUsingContext( + 'build apk uses selected local engine,the engine abi is x64', () async { + when(mockArtifacts.getArtifactPath( + Artifact.flutterFramework, + platform: anyNamed('platform'), + mode: anyNamed('mode'), + environmentType: anyNamed('environmentType'), + )).thenReturn('engine'); + when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x64')); + + fileSystem.file('out/android_x64/flutter_embedding_release.pom') + ..createSync(recursive: true) + ..writeAsStringSync(''' + + + 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b + + + +'''); + fileSystem.file('out/android_x64/x86_64_release.pom').createSync(recursive: true); + fileSystem.file('out/android_x64/x86_64_release.jar').createSync(recursive: true); + fileSystem.file('out/android_x64/x86_64_release.maven-metadata.xml').createSync(recursive: true); + fileSystem.file('out/android_x64/flutter_embedding_release.jar').createSync(recursive: true); + fileSystem.file('out/android_x64/flutter_embedding_release.pom').createSync(recursive: true); + fileSystem.file('out/android_x64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); + + fileSystem.file('android/gradlew').createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.file('android/build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + // Let any process start. Assert after. + when(mockProcessManager.run( + any, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); + + when(mockProcessManager.start(any, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenAnswer((_) { + return Future.value( + createMockProcess( + exitCode: 1, + ) + ); + }); + + await expectLater(() async { + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: const [], + ); + }, throwsToolExit()); + + final List actualGradlewCall = verify( + mockProcessManager.start( + captureAny, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory') + ), + ).captured.last as List; + + expect(actualGradlewCall, contains('/android/gradlew')); + expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x64')); + expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); + expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); + + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + AndroidStudio: () => mockAndroidStudio, + Artifacts: () => mockArtifacts, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + ProcessManager: () => mockProcessManager, + }); + + testUsingContext('honors --no-android-gradle-daemon setting', () async { + (globals.processManager as FakeProcessManager).addCommand( + const FakeCommand(command: [ + '/android/gradlew', + '-q', + '--no-daemon', + '-Ptarget-platform=android-arm,android-arm64,android-x64', + '-Ptarget=lib/main.dart', + '-Pdart-obfuscation=false', + '-Ptrack-widget-creation=false', + '-Ptree-shake-icons=false', + 'assembleRelease' + ], + )); + fileSystem.file('android/gradlew').createSync(recursive: true); + + fileSystem.directory('android') + .childFile('gradle.properties') + .createSync(recursive: true); + + fileSystem.file('android/build.gradle') + .createSync(recursive: true); + + fileSystem.directory('android') + .childDirectory('app') + .childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + await expectLater(() async { + await builder.buildGradleApp( + project: FlutterProject.current(), + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + androidGradleDaemon: false, + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + localGradleErrors: const [], + ); + }, throwsToolExit()); + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + AndroidStudio: () => mockAndroidStudio, + Artifacts: () => Artifacts.test(), + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.list([]), + }); + + testUsingContext('build aar uses selected local engine,the engine abi is arm', () async { + when(mockArtifacts.getArtifactPath( + Artifact.flutterFramework, + platform: TargetPlatform.android_arm, + mode: anyNamed('mode'), + environmentType: anyNamed('environmentType'), + )).thenReturn('engine'); + when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm')); + + fileSystem.file('out/android_arm/flutter_embedding_release.pom') + ..createSync(recursive: true) + ..writeAsStringSync(''' + + + 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b + + + +'''); + fileSystem.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true); + fileSystem.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true); + fileSystem.file('out/android_arm/armeabi_v7a_release.maven-metadata.xml').createSync(recursive: true); + fileSystem.file('out/android_arm/flutter_embedding_release.jar').createSync(recursive: true); + fileSystem.file('out/android_arm/flutter_embedding_release.pom').createSync(recursive: true); + fileSystem.file('out/android_arm/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); + + final File manifestFile = fileSystem.file('pubspec.yaml'); + manifestFile.createSync(recursive: true); + manifestFile.writeAsStringSync(''' + flutter: + module: + androidPackage: com.example.test + ''' + ); + + fileSystem.directory('.android/gradle') + .createSync(recursive: true); + + fileSystem.directory('.android/gradle/wrapper') + .createSync(recursive: true); + + fileSystem.file('.android/gradlew').createSync(recursive: true); + + fileSystem.file('.android/gradle.properties') + .writeAsStringSync('irrelevant'); + + fileSystem.file('.android/build.gradle') + .createSync(recursive: true); + + // Let any process start. Assert after. + when(mockProcessManager.run( + any, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); + + fileSystem.directory('build/outputs/repo').createSync(recursive: true); + + when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null); + + await builder.buildGradleAar( + androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)), + project: FlutterProject.current(), + outputDirectory: fileSystem.directory('build/'), + target: '', + buildNumber: '2.0', + ); + + final List actualGradlewCall = verify( + mockProcessManager.run( + captureAny, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + ), + ).captured.last as List; + + expect(actualGradlewCall, contains('/.android/gradlew')); + expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm')); + expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); + expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); + expect(actualGradlewCall, contains('-PbuildNumber=2.0')); + + // Verify the local engine repo is copied into the generated Maven repo. + final List copyDirectoryArguments = verify( + fileSystemUtils.copyDirectorySync(captureAny, captureAny) + ).captured; + + expect(copyDirectoryArguments.length, 2); + expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0'); + expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo'); + + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + AndroidStudio: () => mockAndroidStudio, + Artifacts: () => mockArtifacts, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + FileSystemUtils: () => fileSystemUtils, + ProcessManager: () => mockProcessManager, + }); + + testUsingContext( + 'build aar uses selected local engine,the engine abi is arm64', () async { + when(mockArtifacts.getArtifactPath( + Artifact.flutterFramework, + platform: anyNamed('platform'), + mode: anyNamed('mode'), + environmentType: anyNamed('environmentType'), + )).thenReturn('engine'); + when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm64')); + + fileSystem.file('out/android_arm64/flutter_embedding_release.pom') + ..createSync(recursive: true) + ..writeAsStringSync(''' + + + 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b + + + +'''); + fileSystem.file('out/android_arm64/arm64_v8a_release.pom').createSync(recursive: true); + fileSystem.file('out/android_arm64/arm64_v8a_release.jar').createSync(recursive: true); + fileSystem.file('out/android_arm64/arm64_v8a_release.maven-metadata.xml').createSync(recursive: true); + fileSystem.file('out/android_arm64/flutter_embedding_release.jar').createSync(recursive: true); + fileSystem.file('out/android_arm64/flutter_embedding_release.pom').createSync(recursive: true); + fileSystem.file('out/android_arm64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); + + final File manifestFile = fileSystem.file('pubspec.yaml'); + manifestFile.createSync(recursive: true); + manifestFile.writeAsStringSync(''' + flutter: + module: + androidPackage: com.example.test + ''' + ); + + fileSystem.directory('.android/gradle') + .createSync(recursive: true); + + fileSystem.directory('.android/gradle/wrapper') + .createSync(recursive: true); + + fileSystem.file('.android/gradlew').createSync(recursive: true); + + fileSystem.file('.android/gradle.properties') + .writeAsStringSync('irrelevant'); + + fileSystem.file('.android/build.gradle') + .createSync(recursive: true); + + // Let any process start. Assert after. + when(mockProcessManager.run( + any, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); + + fileSystem.directory('build/outputs/repo').createSync(recursive: true); + + when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null); + + await builder.buildGradleAar( + androidBuildInfo: const AndroidBuildInfo( + BuildInfo(BuildMode.release, null, treeShakeIcons: false)), + project: FlutterProject.current(), + outputDirectory: fileSystem.directory('build/'), + target: '', + buildNumber: '2.0', + ); + + final List actualGradlewCall = verify( + mockProcessManager.run( + captureAny, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + ), + ).captured.last as List; + + expect(actualGradlewCall, contains('/.android/gradlew')); + expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm64')); + expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); + expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); + expect(actualGradlewCall, contains('-PbuildNumber=2.0')); + + // Verify the local engine repo is copied into the generated Maven repo. + final List copyDirectoryArguments = verify( + fileSystemUtils.copyDirectorySync(captureAny, captureAny) + ).captured; + + expect(copyDirectoryArguments.length, 2); + expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0'); + expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo'); + + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + AndroidStudio: () => mockAndroidStudio, + Artifacts: () => mockArtifacts, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + FileSystemUtils: () => fileSystemUtils, + ProcessManager: () => mockProcessManager, + }); + + testUsingContext( + 'build aar uses selected local engine,the engine abi is x86', () async { + when(mockArtifacts.getArtifactPath( + Artifact.flutterFramework, + platform: anyNamed('platform'), + mode: anyNamed('mode'), + environmentType: anyNamed('environmentType'), + )).thenReturn('engine'); + when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x86')); + + fileSystem.file('out/android_x86/flutter_embedding_release.pom') + ..createSync(recursive: true) + ..writeAsStringSync(''' + + + 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b + + + +'''); + fileSystem.file('out/android_x86/x86_release.pom').createSync(recursive: true); + fileSystem.file('out/android_x86/x86_release.jar').createSync(recursive: true); + fileSystem.file('out/android_x86/x86_release.maven-metadata.xml').createSync(recursive: true); + fileSystem.file('out/android_x86/flutter_embedding_release.jar').createSync(recursive: true); + fileSystem.file('out/android_x86/flutter_embedding_release.pom').createSync(recursive: true); + fileSystem.file('out/android_x86/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); + + final File manifestFile = fileSystem.file('pubspec.yaml'); + manifestFile.createSync(recursive: true); + manifestFile.writeAsStringSync(''' + flutter: + module: + androidPackage: com.example.test + ''' + ); + + fileSystem.directory('.android/gradle') + .createSync(recursive: true); + + fileSystem.directory('.android/gradle/wrapper') + .createSync(recursive: true); + + fileSystem.file('.android/gradlew').createSync(recursive: true); + + fileSystem.file('.android/gradle.properties') + .writeAsStringSync('irrelevant'); + + fileSystem.file('.android/build.gradle') + .createSync(recursive: true); + + // Let any process start. Assert after. + when(mockProcessManager.run( + any, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); + + fileSystem.directory('build/outputs/repo').createSync(recursive: true); + + when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null); + + await builder.buildGradleAar( + androidBuildInfo: const AndroidBuildInfo( + BuildInfo(BuildMode.release, null, treeShakeIcons: false)), + project: FlutterProject.current(), + outputDirectory: fileSystem.directory('build/'), + target: '', + buildNumber: '2.0', + ); + + final List actualGradlewCall = verify( + mockProcessManager.run( + captureAny, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + ), + ).captured.last as List; + + expect(actualGradlewCall, contains('/.android/gradlew')); + expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x86')); + expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); + expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); + expect(actualGradlewCall, contains('-PbuildNumber=2.0')); + + // Verify the local engine repo is copied into the generated Maven repo. + final List copyDirectoryArguments = verify( + fileSystemUtils.copyDirectorySync(captureAny, captureAny) + ).captured; + + expect(copyDirectoryArguments.length, 2); + expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0'); + expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo'); + + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + AndroidStudio: () => mockAndroidStudio, + Artifacts: () => mockArtifacts, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + FileSystemUtils: () => fileSystemUtils, + ProcessManager: () => mockProcessManager, + }); + + testUsingContext( + 'build aar uses selected local engine,the engine abi is x64', () async { + when(mockArtifacts.getArtifactPath( + Artifact.flutterFramework, + platform: anyNamed('platform'), + mode: anyNamed('mode'), + environmentType: anyNamed('environmentType'), + )).thenReturn('engine'); + when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x64')); + + fileSystem.file('out/android_x64/flutter_embedding_release.pom') + ..createSync(recursive: true) + ..writeAsStringSync(''' + + + 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b + + + +'''); + fileSystem.file('out/android_x64/x86_64_release.pom').createSync(recursive: true); + fileSystem.file('out/android_x64/x86_64_release.jar').createSync(recursive: true); + fileSystem.file('out/android_x64/x86_64_release.maven-metadata.xml').createSync(recursive: true); + fileSystem.file('out/android_x64/flutter_embedding_release.jar').createSync(recursive: true); + fileSystem.file('out/android_x64/flutter_embedding_release.pom').createSync(recursive: true); + fileSystem.file('out/android_x64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); + + final File manifestFile = fileSystem.file('pubspec.yaml'); + manifestFile.createSync(recursive: true); + manifestFile.writeAsStringSync(''' + flutter: + module: + androidPackage: com.example.test + ''' + ); + + fileSystem.directory('.android/gradle') + .createSync(recursive: true); + + fileSystem.directory('.android/gradle/wrapper') + .createSync(recursive: true); + + fileSystem.file('.android/gradlew').createSync(recursive: true); + + fileSystem.file('.android/gradle.properties') + .writeAsStringSync('irrelevant'); + + fileSystem.file('.android/build.gradle') + .createSync(recursive: true); + + // Let any process start. Assert after. + when(mockProcessManager.run( + any, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); + + fileSystem.directory('build/outputs/repo').createSync(recursive: true); + + when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null); + + await builder.buildGradleAar( + androidBuildInfo: const AndroidBuildInfo( + BuildInfo(BuildMode.release, null, treeShakeIcons: false)), + project: FlutterProject.current(), + outputDirectory: fileSystem.directory('build/'), + target: '', + buildNumber: '2.0', + ); + + final List actualGradlewCall = verify( + mockProcessManager.run( + captureAny, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + ), + ).captured.last as List; + + expect(actualGradlewCall, contains('/.android/gradlew')); + expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x64')); + expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); + expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); + expect(actualGradlewCall, contains('-PbuildNumber=2.0')); + + // Verify the local engine repo is copied into the generated Maven repo. + final List copyDirectoryArguments = verify( + fileSystemUtils.copyDirectorySync(captureAny, captureAny) + ).captured; + + expect(copyDirectoryArguments.length, 2); + expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0'); + expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo'); + + }, overrides: { + AndroidSdk: () => mockAndroidSdk, + AndroidStudio: () => mockAndroidStudio, + Artifacts: () => mockArtifacts, + Cache: () => cache, + Platform: () => android, + FileSystem: () => fileSystem, + FileSystemUtils: () => fileSystemUtils, + ProcessManager: () => mockProcessManager, + }); + }); +} + +FakePlatform fakePlatform(String name) { + return FakePlatform( + environment: {'HOME': '/path/to/home'}, + operatingSystem: name, + stdoutSupportsAnsi: false, + ); +} + +class MockProcessManager extends Mock implements ProcessManager {} +class MockAndroidSdk extends Mock implements AndroidSdk {} +class MockAndroidStudio extends Mock implements AndroidStudio {} +class MockFileSystemUtils extends Mock implements FileSystemUtils {} +class MockLocalEngineArtifacts extends Mock implements LocalEngineArtifacts {} diff --git a/packages/flutter_tools/test/general.shard/android/gradle_test.dart b/packages/flutter_tools/test/general.shard/android/gradle_test.dart index 475b03c798..ecefbee840 100644 --- a/packages/flutter_tools/test/general.shard/android/gradle_test.dart +++ b/packages/flutter_tools/test/general.shard/android/gradle_test.dart @@ -4,10 +4,8 @@ // @dart = 2.8 -import 'package:archive/archive.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; -import 'package:flutter_tools/src/android/android_studio.dart'; import 'package:flutter_tools/src/android/gradle.dart'; import 'package:flutter_tools/src/android/gradle_errors.dart'; import 'package:flutter_tools/src/android/gradle_utils.dart'; @@ -15,7 +13,6 @@ 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/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/terminal.dart'; @@ -29,7 +26,6 @@ import 'package:process/process.dart'; import '../../src/common.dart'; import '../../src/context.dart'; -import '../../src/mocks.dart'; void main() { Cache.flutterRoot = getFlutterRoot(); @@ -837,12 +833,14 @@ flutter: FileSystem fs; FakeProcessManager fakeProcessManager; MockAndroidSdk mockAndroidSdk; + AndroidGradleBuilder builder; setUp(() { fs = MemoryFileSystem.test(); fakeProcessManager = FakeProcessManager.list([]); mockAndroidSdk = MockAndroidSdk(); when(mockAndroidSdk.directory).thenReturn('irrelevant'); + builder = AndroidGradleBuilder(); }); testUsingContext('calls gradle', () async { @@ -942,7 +940,7 @@ plugin2=${plugin2.path} workingDirectory: plugin2.childDirectory('android').path, )); - await buildPluginsAsAar( + await builder.buildPluginsAsAar( FlutterProject.fromPath(androidDirectory.path), const AndroidBuildInfo(BuildInfo( BuildMode.release, @@ -995,7 +993,7 @@ plugin1=${plugin1.path} .childDirectory('repo') .createSync(recursive: true); - await buildPluginsAsAar( + await builder.buildPluginsAsAar( FlutterProject.fromPath(androidDirectory.path), const AndroidBuildInfo(BuildInfo.release), buildDirectory: buildDirectory, @@ -1009,1612 +1007,6 @@ plugin1=${plugin1.path} }); }); - group('gradle build', () { - TestUsage testUsage; - MockAndroidSdk mockAndroidSdk; - MockAndroidStudio mockAndroidStudio; - MockLocalEngineArtifacts mockArtifacts; - MockProcessManager mockProcessManager; - FakePlatform android; - FileSystem fileSystem; - FileSystemUtils fileSystemUtils; - Cache cache; - - setUp(() { - testUsage = TestUsage(); - fileSystem = MemoryFileSystem.test(); - fileSystemUtils = MockFileSystemUtils(); - mockAndroidSdk = MockAndroidSdk(); - mockAndroidStudio = MockAndroidStudio(); - mockArtifacts = MockLocalEngineArtifacts(); - mockProcessManager = MockProcessManager(); - android = fakePlatform('android'); - - when(mockAndroidSdk.directory).thenReturn('irrelevant'); - - final Directory rootDirectory = fileSystem.currentDirectory; - cache = Cache.test( - rootOverride: rootDirectory, - fileSystem: fileSystem, - ); - - final Directory gradleWrapperDirectory = rootDirectory - .childDirectory('bin') - .childDirectory('cache') - .childDirectory('artifacts') - .childDirectory('gradle_wrapper'); - gradleWrapperDirectory.createSync(recursive: true); - gradleWrapperDirectory - .childFile('gradlew') - .writeAsStringSync('irrelevant'); - gradleWrapperDirectory - .childDirectory('gradle') - .childDirectory('wrapper') - .createSync(recursive: true); - gradleWrapperDirectory - .childDirectory('gradle') - .childDirectory('wrapper') - .childFile('gradle-wrapper.jar') - .writeAsStringSync('irrelevant'); - }); - - testUsingContext('recognizes common errors - tool exit', () async { - final Process process = createMockProcess( - exitCode: 1, - stdout: 'irrelevant\nSome gradle message\nirrelevant', - ); - when(mockProcessManager.start(any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'))) - .thenAnswer((_) => Future.value(process)); - - fileSystem.directory('android') - .childFile('build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - bool handlerCalled = false; - await expectLater(() async { - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - ), - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: [ - GradleHandledError( - test: (String line) { - return line.contains('Some gradle message'); - }, - handler: ({ - String line, - FlutterProject project, - bool usesAndroidX, - bool shouldBuildPluginAsAar, - }) async { - handlerCalled = true; - return GradleBuildStatus.exit; - }, - eventLabel: 'random-event-label', - ), - ], - ); - }, - throwsToolExit( - message: 'Gradle task assembleRelease failed with exit code 1' - )); - - expect(handlerCalled, isTrue); - - expect(testUsage.events, contains( - const TestUsageEvent( - 'build', - 'unspecified', - label: 'gradle-random-event-label-failure', - parameters: {}, - ), - )); - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - ProcessManager: () => mockProcessManager, - Usage: () => testUsage, - }); - - testUsingContext('recognizes common errors - retry build', () async { - when(mockProcessManager.start(any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'))) - .thenAnswer((_) { - final Process process = createMockProcess( - exitCode: 1, - stdout: 'irrelevant\nSome gradle message\nirrelevant', - ); - return Future.value(process); - }); - - fileSystem.directory('android') - .childFile('build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - int testFnCalled = 0; - await expectLater(() async { - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - ), - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: [ - GradleHandledError( - test: (String line) { - if (line.contains('Some gradle message')) { - testFnCalled++; - return true; - } - return false; - }, - handler: ({ - String line, - FlutterProject project, - bool usesAndroidX, - bool shouldBuildPluginAsAar, - }) async { - return GradleBuildStatus.retry; - }, - eventLabel: 'random-event-label', - ), - ], - ); - }, throwsToolExit( - message: 'Gradle task assembleRelease failed with exit code 1' - )); - - expect(testFnCalled, equals(2)); - expect(testUsage.events, contains( - const TestUsageEvent( - 'build', - 'unspecified', - label: 'gradle-random-event-label-failure', - parameters: {}, - ), - )); - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - ProcessManager: () => mockProcessManager, - Usage: () => testUsage, - }); - - testUsingContext('recognizes process exceptions - tool exit', () async { - when(mockProcessManager.start(any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'))) - .thenThrow(const ProcessException('', [], 'Some gradle message')); - - fileSystem.directory('android') - .childFile('build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - bool handlerCalled = false; - await expectLater(() async { - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - ), - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: [ - GradleHandledError( - test: (String line) { - return line.contains('Some gradle message'); - }, - handler: ({ - String line, - FlutterProject project, - bool usesAndroidX, - bool shouldBuildPluginAsAar, - }) async { - handlerCalled = true; - return GradleBuildStatus.exit; - }, - eventLabel: 'random-event-label', - ), - ], - ); - }, - throwsToolExit( - message: 'Gradle task assembleRelease failed with exit code 1' - )); - - expect(handlerCalled, isTrue); - - expect(testUsage.events, contains( - const TestUsageEvent( - 'build', - 'unspecified', - label: 'gradle-random-event-label-failure', - parameters: {}, - ), - )); - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - ProcessManager: () => mockProcessManager, - Usage: () => testUsage, - }); - - testUsingContext('rethrows unrecognized ProcessException', () async { - when(mockProcessManager.start(any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'))) - .thenThrow(const ProcessException('', [], 'Unrecognized')); - - fileSystem.directory('android') - .childFile('build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - await expectLater(() async { - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - ), - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: const [], - ); - }, - throwsA(isA())); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - ProcessManager: () => mockProcessManager, - }); - - testUsingContext('logs success event after a successful retry', () async { - int testFnCalled = 0; - when(mockProcessManager.start(any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'))) - .thenAnswer((_) { - Process process; - if (testFnCalled == 0) { - process = createMockProcess( - exitCode: 1, - stdout: 'irrelevant\nSome gradle message\nirrelevant', - ); - } else { - process = createMockProcess( - exitCode: 0, - stdout: 'irrelevant', - ); - } - testFnCalled++; - return Future.value(process); - }); - - fileSystem.directory('android') - .childFile('build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - fileSystem.directory('build') - .childDirectory('app') - .childDirectory('outputs') - .childDirectory('flutter-apk') - .childFile('app-release.apk') - .createSync(recursive: true); - - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - ), - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: [ - GradleHandledError( - test: (String line) { - return line.contains('Some gradle message'); - }, - handler: ({ - String line, - FlutterProject project, - bool usesAndroidX, - bool shouldBuildPluginAsAar, - }) async { - return GradleBuildStatus.retry; - }, - eventLabel: 'random-event-label', - ), - ], - ); - - expect(testUsage.events, contains( - const TestUsageEvent( - 'build', - 'unspecified', - label: 'gradle-random-event-label-success', - parameters: {}, - ), - )); - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - Cache: () => cache, - FileSystem: () => fileSystem, - Platform: () => android, - ProcessManager: () => mockProcessManager, - Usage: () => testUsage, - }); - - testUsingContext('performs code size analysis and sends analytics', () async { - when(mockProcessManager.start(any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'))) - .thenAnswer((_) { - return Future.value(createMockProcess( - exitCode: 0, - stdout: 'irrelevant', - )); - }); - - fileSystem.directory('android') - .childFile('build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - final Archive archive = Archive() - ..addFile(ArchiveFile('AndroidManifest.xml', 100, List.filled(100, 0))) - ..addFile(ArchiveFile('META-INF/CERT.RSA', 10, List.filled(10, 0))) - ..addFile(ArchiveFile('META-INF/CERT.SF', 10, List.filled(10, 0))) - ..addFile(ArchiveFile('lib/arm64-v8a/libapp.so', 50, List.filled(50, 0))) - ..addFile(ArchiveFile('lib/arm64-v8a/libflutter.so', 50, List.filled(50, 0))); - - fileSystem.directory('build') - .childDirectory('app') - .childDirectory('outputs') - .childDirectory('flutter-apk') - .childFile('app-release.apk') - ..createSync(recursive: true) - ..writeAsBytesSync(ZipEncoder().encode(archive)); - - fileSystem.file('foo/snapshot.arm64-v8a.json') - ..createSync(recursive: true) - ..writeAsStringSync(r''' -[ - { - "l": "dart:_internal", - "c": "SubListIterable", - "n": "[Optimized] skip", - "s": 2400 - } -]'''); - fileSystem.file('foo/trace.arm64-v8a.json') - ..createSync(recursive: true) - ..writeAsStringSync('{}'); - - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - codeSizeDirectory: 'foo', - ), - targetArchs: [AndroidArch.arm64_v8a], - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: [], - ); - - expect(testUsage.events, contains( - const TestUsageEvent( - 'code-size-analysis', - 'apk', - ), - )); - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - Cache: () => cache, - FileSystem: () => fileSystem, - Platform: () => android, - ProcessManager: () => mockProcessManager, - Usage: () => testUsage, - }); - - testUsingContext('recognizes common errors - retry build with AAR plugins', () async { - when(mockProcessManager.start(any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'))) - .thenAnswer((_) { - final Process process = createMockProcess( - exitCode: 1, - stdout: 'irrelevant\nSome gradle message\nirrelevant', - ); - return Future.value(process); - }); - - fileSystem.directory('android') - .childFile('build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - int testFnCalled = 0; - bool builtPluginAsAar = false; - await expectLater(() async { - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - ), - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: [ - GradleHandledError( - test: (String line) { - if (line.contains('Some gradle message')) { - testFnCalled++; - return true; - } - return false; - }, - handler: ({ - String line, - FlutterProject project, - bool usesAndroidX, - bool shouldBuildPluginAsAar, - }) async { - if (testFnCalled == 2) { - builtPluginAsAar = shouldBuildPluginAsAar; - } - return GradleBuildStatus.retryWithAarPlugins; - }, - eventLabel: 'random-event-label', - ), - ], - ); - }, throwsToolExit( - message: 'Gradle task assembleRelease failed with exit code 1' - )); - - expect(testFnCalled, equals(2)); - expect(builtPluginAsAar, isTrue); - - expect(testUsage.events, contains( - const TestUsageEvent( - 'build', - 'unspecified', - label: 'gradle-random-event-label-failure', - parameters: {}, - ), - )); - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - ProcessManager: () => mockProcessManager, - Usage: () => testUsage, - }); - - testUsingContext('indicates that an APK has been built successfully', () async { - fileSystem.directory('android') - .childFile('build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - fileSystem.directory('build') - .childDirectory('app') - .childDirectory('outputs') - .childDirectory('flutter-apk') - .childFile('app-release.apk') - .createSync(recursive: true); - - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - ), - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: const [], - ); - - expect( - testLogger.statusText, - contains('Built build/app/outputs/flutter-apk/app-release.apk (0.0MB)'), - ); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - Cache: () => cache, - FileSystem: () => fileSystem, - Platform: () => android, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext("doesn't indicate how to consume an AAR when printHowToConsumeAaar is false", () async { - final File manifestFile = fileSystem.file('pubspec.yaml'); - manifestFile.createSync(recursive: true); - manifestFile.writeAsStringSync(''' - flutter: - module: - androidPackage: com.example.test - ''' - ); - - fileSystem.file('.android/gradlew').createSync(recursive: true); - - fileSystem.file('.android/gradle.properties') - .writeAsStringSync('irrelevant'); - - fileSystem.file('.android/build.gradle') - .createSync(recursive: true); - - // Let any process start. Assert after. - when(mockProcessManager.run( - any, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); - - fileSystem.directory('build/outputs/repo').createSync(recursive: true); - - await buildGradleAar( - androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)), - project: FlutterProject.current(), - outputDirectory: fileSystem.directory('build/'), - target: '', - buildNumber: '1.0', - ); - - expect( - testLogger.statusText, - contains('Built build/outputs/repo'), - ); - expect( - testLogger.statusText.contains('Consuming the Module'), - isFalse, - ); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - AndroidStudio: () => mockAndroidStudio, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - ProcessManager: () => mockProcessManager, - }); - - testUsingContext('gradle exit code is properly passed on', () async { - final File manifestFile = fileSystem.file('pubspec.yaml'); - manifestFile.createSync(recursive: true); - manifestFile.writeAsStringSync(''' - flutter: - module: - androidPackage: com.example.test - ''' - ); - - fileSystem.file('.android/gradlew').createSync(recursive: true); - - fileSystem.file('.android/gradle.properties') - .writeAsStringSync('irrelevant'); - - fileSystem.file('.android/build.gradle') - .createSync(recursive: true); - - // Let any process start. Assert after. - when(mockProcessManager.run( - any, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )).thenAnswer((_) async => ProcessResult(1, 108, '', '')); - - fileSystem.directory('build/outputs/repo').createSync(recursive: true); - - await expectLater(() async => - await buildGradleAar( - androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)), - project: FlutterProject.current(), - outputDirectory: fileSystem.directory('build/'), - target: '', - buildNumber: '1.0', - ) - , throwsToolExit(exitCode: 108, message: 'Gradle task assembleAarRelease failed with exit code 108.')); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - AndroidStudio: () => mockAndroidStudio, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - ProcessManager: () => mockProcessManager, - }); - - testUsingContext('build apk uses selected local engine,the engine abi is arm', () async { - when(mockArtifacts.getArtifactPath( - Artifact.flutterFramework, - platform: TargetPlatform.android_arm, - mode: anyNamed('mode'), - environmentType: anyNamed('environmentType'), - )).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm')); - - fileSystem.file('out/android_arm/flutter_embedding_release.pom') - ..createSync(recursive: true) - ..writeAsStringSync(''' - - - 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b - - - -'''); - fileSystem.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true); - fileSystem.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true); - fileSystem.file('out/android_arm/armeabi_v7a_release.maven-metadata.xml').createSync(recursive: true); - fileSystem.file('out/android_arm/flutter_embedding_release.jar').createSync(recursive: true); - fileSystem.file('out/android_arm/flutter_embedding_release.pom').createSync(recursive: true); - fileSystem.file('out/android_arm/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); - - fileSystem.file('android/gradlew').createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.file('android/build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - // Let any process start. Assert after. - when(mockProcessManager.run( - any, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); - - when(mockProcessManager.start(any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'))) - .thenAnswer((_) { - return Future.value( - createMockProcess( - exitCode: 1, - ) - ); - }); - - await expectLater(() async { - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - ), - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: const [], - ); - }, throwsToolExit()); - - final List actualGradlewCall = verify( - mockProcessManager.start( - captureAny, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory') - ), - ).captured.last as List; - - expect(actualGradlewCall, contains('/android/gradlew')); - expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm')); - expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); - expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - AndroidStudio: () => mockAndroidStudio, - Artifacts: () => mockArtifacts, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - ProcessManager: () => mockProcessManager, - }); - - testUsingContext( - 'build apk uses selected local engine,the engine abi is arm64', () async { - when(mockArtifacts.getArtifactPath( - Artifact.flutterFramework, - platform: anyNamed('platform'), - mode: anyNamed('mode'), - environmentType: anyNamed('environmentType'), - )).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm64')); - - fileSystem.file('out/android_arm64/flutter_embedding_release.pom') - ..createSync(recursive: true) - ..writeAsStringSync(''' - - - 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b - - - -'''); - fileSystem.file('out/android_arm64/arm64_v8a_release.pom').createSync(recursive: true); - fileSystem.file('out/android_arm64/arm64_v8a_release.jar').createSync(recursive: true); - fileSystem.file('out/android_arm64/arm64_v8a_release.maven-metadata.xml').createSync(recursive: true); - fileSystem.file('out/android_arm64/flutter_embedding_release.jar').createSync(recursive: true); - fileSystem.file('out/android_arm64/flutter_embedding_release.pom').createSync(recursive: true); - fileSystem.file('out/android_arm64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); - - fileSystem.file('android/gradlew').createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.file('android/build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - // Let any process start. Assert after. - when(mockProcessManager.run( - any, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); - - when(mockProcessManager.start(any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'))) - .thenAnswer((_) { - return Future.value( - createMockProcess( - exitCode: 1, - ) - ); - }); - - await expectLater(() async { - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - ), - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: const [], - ); - }, throwsToolExit()); - - final List actualGradlewCall = verify( - mockProcessManager.start( - captureAny, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory') - ), - ).captured.last as List; - - expect(actualGradlewCall, contains('/android/gradlew')); - expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm64')); - expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); - expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - AndroidStudio: () => mockAndroidStudio, - Artifacts: () => mockArtifacts, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - ProcessManager: () => mockProcessManager, - }); - - testUsingContext( - 'build apk uses selected local engine,the engine abi is x86', () async { - when(mockArtifacts.getArtifactPath( - Artifact.flutterFramework, - platform: anyNamed('platform'), - mode: anyNamed('mode'), - environmentType: anyNamed('environmentType'), - )).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x86')); - - fileSystem.file('out/android_x86/flutter_embedding_release.pom') - ..createSync(recursive: true) - ..writeAsStringSync(''' - - - 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b - - - -'''); - fileSystem.file('out/android_x86/x86_release.pom').createSync(recursive: true); - fileSystem.file('out/android_x86/x86_release.jar').createSync(recursive: true); - fileSystem.file('out/android_x86/x86_release.maven-metadata.xml').createSync(recursive: true); - fileSystem.file('out/android_x86/flutter_embedding_release.jar').createSync(recursive: true); - fileSystem.file('out/android_x86/flutter_embedding_release.pom').createSync(recursive: true); - fileSystem.file('out/android_x86/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); - - fileSystem.file('android/gradlew').createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.file('android/build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - // Let any process start. Assert after. - when(mockProcessManager.run( - any, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); - - when(mockProcessManager.start(any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'))) - .thenAnswer((_) { - return Future.value( - createMockProcess( - exitCode: 1, - ) - ); - }); - - await expectLater(() async { - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - ), - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: const [], - ); - }, throwsToolExit()); - - final List actualGradlewCall = verify( - mockProcessManager.start( - captureAny, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory') - ), - ).captured.last as List; - - expect(actualGradlewCall, contains('/android/gradlew')); - expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x86')); - expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); - expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - AndroidStudio: () => mockAndroidStudio, - Artifacts: () => mockArtifacts, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - ProcessManager: () => mockProcessManager, - }); - - testUsingContext( - 'build apk uses selected local engine,the engine abi is x64', () async { - when(mockArtifacts.getArtifactPath( - Artifact.flutterFramework, - platform: anyNamed('platform'), - mode: anyNamed('mode'), - environmentType: anyNamed('environmentType'), - )).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x64')); - - fileSystem.file('out/android_x64/flutter_embedding_release.pom') - ..createSync(recursive: true) - ..writeAsStringSync(''' - - - 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b - - - -'''); - fileSystem.file('out/android_x64/x86_64_release.pom').createSync(recursive: true); - fileSystem.file('out/android_x64/x86_64_release.jar').createSync(recursive: true); - fileSystem.file('out/android_x64/x86_64_release.maven-metadata.xml').createSync(recursive: true); - fileSystem.file('out/android_x64/flutter_embedding_release.jar').createSync(recursive: true); - fileSystem.file('out/android_x64/flutter_embedding_release.pom').createSync(recursive: true); - fileSystem.file('out/android_x64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); - - fileSystem.file('android/gradlew').createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.file('android/build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - // Let any process start. Assert after. - when(mockProcessManager.run( - any, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); - - when(mockProcessManager.start(any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'))) - .thenAnswer((_) { - return Future.value( - createMockProcess( - exitCode: 1, - ) - ); - }); - - await expectLater(() async { - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - ), - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: const [], - ); - }, throwsToolExit()); - - final List actualGradlewCall = verify( - mockProcessManager.start( - captureAny, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory') - ), - ).captured.last as List; - - expect(actualGradlewCall, contains('/android/gradlew')); - expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x64')); - expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); - expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - AndroidStudio: () => mockAndroidStudio, - Artifacts: () => mockArtifacts, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - ProcessManager: () => mockProcessManager, - }); - - testUsingContext('honors --no-android-gradle-daemon setting', () async { - (globals.processManager as FakeProcessManager).addCommand( - const FakeCommand(command: [ - '/android/gradlew', - '-q', - '--no-daemon', - '-Ptarget-platform=android-arm,android-arm64,android-x64', - '-Ptarget=lib/main.dart', - '-Pdart-obfuscation=false', - '-Ptrack-widget-creation=false', - '-Ptree-shake-icons=false', - 'assembleRelease' - ], - )); - fileSystem.file('android/gradlew').createSync(recursive: true); - - fileSystem.directory('android') - .childFile('gradle.properties') - .createSync(recursive: true); - - fileSystem.file('android/build.gradle') - .createSync(recursive: true); - - fileSystem.directory('android') - .childDirectory('app') - .childFile('build.gradle') - ..createSync(recursive: true) - ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); - - await expectLater(() async { - await buildGradleApp( - project: FlutterProject.current(), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, - null, - treeShakeIcons: false, - androidGradleDaemon: false, - ), - ), - target: 'lib/main.dart', - isBuildingBundle: false, - localGradleErrors: const [], - ); - }, throwsToolExit()); - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - AndroidStudio: () => mockAndroidStudio, - Artifacts: () => Artifacts.test(), - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - ProcessManager: () => FakeProcessManager.list([]), - }); - - testUsingContext('build aar uses selected local engine,the engine abi is arm', () async { - when(mockArtifacts.getArtifactPath( - Artifact.flutterFramework, - platform: TargetPlatform.android_arm, - mode: anyNamed('mode'), - environmentType: anyNamed('environmentType'), - )).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm')); - - fileSystem.file('out/android_arm/flutter_embedding_release.pom') - ..createSync(recursive: true) - ..writeAsStringSync(''' - - - 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b - - - -'''); - fileSystem.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true); - fileSystem.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true); - fileSystem.file('out/android_arm/armeabi_v7a_release.maven-metadata.xml').createSync(recursive: true); - fileSystem.file('out/android_arm/flutter_embedding_release.jar').createSync(recursive: true); - fileSystem.file('out/android_arm/flutter_embedding_release.pom').createSync(recursive: true); - fileSystem.file('out/android_arm/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); - - final File manifestFile = fileSystem.file('pubspec.yaml'); - manifestFile.createSync(recursive: true); - manifestFile.writeAsStringSync(''' - flutter: - module: - androidPackage: com.example.test - ''' - ); - - fileSystem.directory('.android/gradle') - .createSync(recursive: true); - - fileSystem.directory('.android/gradle/wrapper') - .createSync(recursive: true); - - fileSystem.file('.android/gradlew').createSync(recursive: true); - - fileSystem.file('.android/gradle.properties') - .writeAsStringSync('irrelevant'); - - fileSystem.file('.android/build.gradle') - .createSync(recursive: true); - - // Let any process start. Assert after. - when(mockProcessManager.run( - any, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); - - fileSystem.directory('build/outputs/repo').createSync(recursive: true); - - when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null); - - await buildGradleAar( - androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)), - project: FlutterProject.current(), - outputDirectory: fileSystem.directory('build/'), - target: '', - buildNumber: '2.0', - ); - - final List actualGradlewCall = verify( - mockProcessManager.run( - captureAny, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - ), - ).captured.last as List; - - expect(actualGradlewCall, contains('/.android/gradlew')); - expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm')); - expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); - expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); - expect(actualGradlewCall, contains('-PbuildNumber=2.0')); - - // Verify the local engine repo is copied into the generated Maven repo. - final List copyDirectoryArguments = verify( - fileSystemUtils.copyDirectorySync(captureAny, captureAny) - ).captured; - - expect(copyDirectoryArguments.length, 2); - expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0'); - expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo'); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - AndroidStudio: () => mockAndroidStudio, - Artifacts: () => mockArtifacts, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - FileSystemUtils: () => fileSystemUtils, - ProcessManager: () => mockProcessManager, - }); - - testUsingContext( - 'build aar uses selected local engine,the engine abi is arm64', () async { - when(mockArtifacts.getArtifactPath( - Artifact.flutterFramework, - platform: anyNamed('platform'), - mode: anyNamed('mode'), - environmentType: anyNamed('environmentType'), - )).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm64')); - - fileSystem.file('out/android_arm64/flutter_embedding_release.pom') - ..createSync(recursive: true) - ..writeAsStringSync(''' - - - 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b - - - -'''); - fileSystem.file('out/android_arm64/arm64_v8a_release.pom').createSync(recursive: true); - fileSystem.file('out/android_arm64/arm64_v8a_release.jar').createSync(recursive: true); - fileSystem.file('out/android_arm64/arm64_v8a_release.maven-metadata.xml').createSync(recursive: true); - fileSystem.file('out/android_arm64/flutter_embedding_release.jar').createSync(recursive: true); - fileSystem.file('out/android_arm64/flutter_embedding_release.pom').createSync(recursive: true); - fileSystem.file('out/android_arm64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); - - final File manifestFile = fileSystem.file('pubspec.yaml'); - manifestFile.createSync(recursive: true); - manifestFile.writeAsStringSync(''' - flutter: - module: - androidPackage: com.example.test - ''' - ); - - fileSystem.directory('.android/gradle') - .createSync(recursive: true); - - fileSystem.directory('.android/gradle/wrapper') - .createSync(recursive: true); - - fileSystem.file('.android/gradlew').createSync(recursive: true); - - fileSystem.file('.android/gradle.properties') - .writeAsStringSync('irrelevant'); - - fileSystem.file('.android/build.gradle') - .createSync(recursive: true); - - // Let any process start. Assert after. - when(mockProcessManager.run( - any, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); - - fileSystem.directory('build/outputs/repo').createSync(recursive: true); - - when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null); - - await buildGradleAar( - androidBuildInfo: const AndroidBuildInfo( - BuildInfo(BuildMode.release, null, treeShakeIcons: false)), - project: FlutterProject.current(), - outputDirectory: fileSystem.directory('build/'), - target: '', - buildNumber: '2.0', - ); - - final List actualGradlewCall = verify( - mockProcessManager.run( - captureAny, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - ), - ).captured.last as List; - - expect(actualGradlewCall, contains('/.android/gradlew')); - expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm64')); - expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); - expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); - expect(actualGradlewCall, contains('-PbuildNumber=2.0')); - - // Verify the local engine repo is copied into the generated Maven repo. - final List copyDirectoryArguments = verify( - fileSystemUtils.copyDirectorySync(captureAny, captureAny) - ).captured; - - expect(copyDirectoryArguments.length, 2); - expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0'); - expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo'); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - AndroidStudio: () => mockAndroidStudio, - Artifacts: () => mockArtifacts, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - FileSystemUtils: () => fileSystemUtils, - ProcessManager: () => mockProcessManager, - }); - - testUsingContext( - 'build aar uses selected local engine,the engine abi is x86', () async { - when(mockArtifacts.getArtifactPath( - Artifact.flutterFramework, - platform: anyNamed('platform'), - mode: anyNamed('mode'), - environmentType: anyNamed('environmentType'), - )).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x86')); - - fileSystem.file('out/android_x86/flutter_embedding_release.pom') - ..createSync(recursive: true) - ..writeAsStringSync(''' - - - 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b - - - -'''); - fileSystem.file('out/android_x86/x86_release.pom').createSync(recursive: true); - fileSystem.file('out/android_x86/x86_release.jar').createSync(recursive: true); - fileSystem.file('out/android_x86/x86_release.maven-metadata.xml').createSync(recursive: true); - fileSystem.file('out/android_x86/flutter_embedding_release.jar').createSync(recursive: true); - fileSystem.file('out/android_x86/flutter_embedding_release.pom').createSync(recursive: true); - fileSystem.file('out/android_x86/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); - - final File manifestFile = fileSystem.file('pubspec.yaml'); - manifestFile.createSync(recursive: true); - manifestFile.writeAsStringSync(''' - flutter: - module: - androidPackage: com.example.test - ''' - ); - - fileSystem.directory('.android/gradle') - .createSync(recursive: true); - - fileSystem.directory('.android/gradle/wrapper') - .createSync(recursive: true); - - fileSystem.file('.android/gradlew').createSync(recursive: true); - - fileSystem.file('.android/gradle.properties') - .writeAsStringSync('irrelevant'); - - fileSystem.file('.android/build.gradle') - .createSync(recursive: true); - - // Let any process start. Assert after. - when(mockProcessManager.run( - any, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); - - fileSystem.directory('build/outputs/repo').createSync(recursive: true); - - when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null); - - await buildGradleAar( - androidBuildInfo: const AndroidBuildInfo( - BuildInfo(BuildMode.release, null, treeShakeIcons: false)), - project: FlutterProject.current(), - outputDirectory: fileSystem.directory('build/'), - target: '', - buildNumber: '2.0', - ); - - final List actualGradlewCall = verify( - mockProcessManager.run( - captureAny, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - ), - ).captured.last as List; - - expect(actualGradlewCall, contains('/.android/gradlew')); - expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x86')); - expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); - expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); - expect(actualGradlewCall, contains('-PbuildNumber=2.0')); - - // Verify the local engine repo is copied into the generated Maven repo. - final List copyDirectoryArguments = verify( - fileSystemUtils.copyDirectorySync(captureAny, captureAny) - ).captured; - - expect(copyDirectoryArguments.length, 2); - expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0'); - expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo'); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - AndroidStudio: () => mockAndroidStudio, - Artifacts: () => mockArtifacts, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - FileSystemUtils: () => fileSystemUtils, - ProcessManager: () => mockProcessManager, - }); - - testUsingContext( - 'build aar uses selected local engine,the engine abi is x64', () async { - when(mockArtifacts.getArtifactPath( - Artifact.flutterFramework, - platform: anyNamed('platform'), - mode: anyNamed('mode'), - environmentType: anyNamed('environmentType'), - )).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x64')); - - fileSystem.file('out/android_x64/flutter_embedding_release.pom') - ..createSync(recursive: true) - ..writeAsStringSync(''' - - - 1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b - - - -'''); - fileSystem.file('out/android_x64/x86_64_release.pom').createSync(recursive: true); - fileSystem.file('out/android_x64/x86_64_release.jar').createSync(recursive: true); - fileSystem.file('out/android_x64/x86_64_release.maven-metadata.xml').createSync(recursive: true); - fileSystem.file('out/android_x64/flutter_embedding_release.jar').createSync(recursive: true); - fileSystem.file('out/android_x64/flutter_embedding_release.pom').createSync(recursive: true); - fileSystem.file('out/android_x64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true); - - final File manifestFile = fileSystem.file('pubspec.yaml'); - manifestFile.createSync(recursive: true); - manifestFile.writeAsStringSync(''' - flutter: - module: - androidPackage: com.example.test - ''' - ); - - fileSystem.directory('.android/gradle') - .createSync(recursive: true); - - fileSystem.directory('.android/gradle/wrapper') - .createSync(recursive: true); - - fileSystem.file('.android/gradlew').createSync(recursive: true); - - fileSystem.file('.android/gradle.properties') - .writeAsStringSync('irrelevant'); - - fileSystem.file('.android/build.gradle') - .createSync(recursive: true); - - // Let any process start. Assert after. - when(mockProcessManager.run( - any, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); - - fileSystem.directory('build/outputs/repo').createSync(recursive: true); - - when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null); - - await buildGradleAar( - androidBuildInfo: const AndroidBuildInfo( - BuildInfo(BuildMode.release, null, treeShakeIcons: false)), - project: FlutterProject.current(), - outputDirectory: fileSystem.directory('build/'), - target: '', - buildNumber: '2.0', - ); - - final List actualGradlewCall = verify( - mockProcessManager.run( - captureAny, - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - ), - ).captured.last as List; - - expect(actualGradlewCall, contains('/.android/gradlew')); - expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x64')); - expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0')); - expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release')); - expect(actualGradlewCall, contains('-PbuildNumber=2.0')); - - // Verify the local engine repo is copied into the generated Maven repo. - final List copyDirectoryArguments = verify( - fileSystemUtils.copyDirectorySync(captureAny, captureAny) - ).captured; - - expect(copyDirectoryArguments.length, 2); - expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0'); - expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo'); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - AndroidStudio: () => mockAndroidStudio, - Artifacts: () => mockArtifacts, - Cache: () => cache, - Platform: () => android, - FileSystem: () => fileSystem, - FileSystemUtils: () => fileSystemUtils, - ProcessManager: () => mockProcessManager, - }); - }); - group('printHowToConsumeAar', () { BufferLogger logger; FileSystem fileSystem; @@ -2854,8 +1246,6 @@ class FakeGradleUtils extends GradleUtils { class MockAndroidSdk extends Mock implements AndroidSdk {} class MockAndroidProject extends Mock implements AndroidProject {} -class MockAndroidStudio extends Mock implements AndroidStudio {} -class MockFileSystemUtils extends Mock implements FileSystemUtils {} class MockFlutterProject extends Mock implements FlutterProject {} class MockLocalEngineArtifacts extends Mock implements LocalEngineArtifacts {} class MockProcessManager extends Mock implements ProcessManager {}