diff --git a/dev/bots/test.dart b/dev/bots/test.dart index e37521359d..1d2cdc8d0d 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -1022,7 +1022,6 @@ Future _androidGradleTests(String subShard) async { await _runDevicelabTest('gradle_plugin_fat_apk_test', env: env); await _runDevicelabTest('gradle_r8_test', env: env); await _runDevicelabTest('gradle_non_android_plugin_test', env: env); - await _runDevicelabTest('gradle_jetifier_test', env: env); } if (subShard == 'gradle2') { await _runDevicelabTest('gradle_plugin_bundle_test', env: env); diff --git a/dev/devicelab/bin/tasks/gradle_jetifier_test.dart b/dev/devicelab/bin/tasks/gradle_jetifier_test.dart index 86ee0ed5b7..bbabfc2e8b 100644 --- a/dev/devicelab/bin/tasks/gradle_jetifier_test.dart +++ b/dev/devicelab/bin/tasks/gradle_jetifier_test.dart @@ -64,7 +64,6 @@ Future main() async { options: [ 'apk', '--target-platform', 'android-arm', - '--no-shrink', '--verbose', ], ); @@ -101,9 +100,7 @@ Future main() async { options: [ 'apk', '--target-platform', 'android-arm', - '--debug', - '--no-shrink', - '--verbose', + '--debug', '--verbose', ], ); }); diff --git a/packages/flutter_tools/gradle/aar_init_script.gradle b/packages/flutter_tools/gradle/aar_init_script.gradle index 4874aa21b1..2439da81c9 100644 --- a/packages/flutter_tools/gradle/aar_init_script.gradle +++ b/packages/flutter_tools/gradle/aar_init_script.gradle @@ -7,7 +7,6 @@ // destination of the local repository. // The local repository will contain the AAR and POM files. -import java.nio.file.Paths import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.maven.MavenDeployer @@ -40,32 +39,16 @@ void configureProject(Project project, File outputDir) { } } } - if (!project.property("is-plugin").toBoolean()) { - return - } - if (project.hasProperty('localEngineOut')) { - // TODO(egarciad): Support local engine. - // This most likely requires refactoring `flutter.gradle`, so the logic can be reused. - throw new GradleException( - "Local engine isn't supported when building the plugins as AAR. " + - "See: https://github.com/flutter/flutter/issues/40866") - } - - // This is a Flutter plugin project. Plugin projects don't apply the Flutter Gradle plugin, - // as a result, add the dependency on the embedding. - project.repositories { - maven { - url "http://download.flutter.io" - } - } - String engineVersion = Paths.get(getFlutterRoot(project), "bin", "internal", "engine.version") - .toFile().text.trim() - project.dependencies { - // Add the embedding dependency. - compileOnly ("io.flutter:flutter_embedding_release:1.0.0-$engineVersion") { - // We only need to expose io.flutter.plugin.* - // No need for the embedding transitive dependencies. - transitive = false + // Check if the project uses the Flutter plugin (defined in flutter.gradle). + Boolean usesFlutterPlugin = project.plugins.find { it.class.name == "FlutterPlugin" } != null + if (!usesFlutterPlugin) { + project.dependencies { + // Some plugins don't include `annotations` and they don't set + // `android.useAndroidX=true` in `gradle.properties`. + compileOnly "androidx.annotation:annotation:+" + compileOnly "com.android.support:support-annotations:+" + // The Flutter plugin already adds `flutter.jar`. + compileOnly project.files("${getFlutterRoot(project)}/bin/cache/artifacts/engine/android-arm-release/flutter.jar") } } } diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle index 606d3a3bd2..c1067bccfa 100644 --- a/packages/flutter_tools/gradle/flutter.gradle +++ b/packages/flutter_tools/gradle/flutter.gradle @@ -8,8 +8,6 @@ import com.android.builder.model.AndroidProject import com.android.build.OutputFile import java.nio.file.Path import java.nio.file.Paths -import java.util.regex.Matcher -import java.util.regex.Pattern import org.apache.tools.ant.taskdefs.condition.Os import org.gradle.api.DefaultTask import org.gradle.api.GradleException @@ -258,65 +256,29 @@ class FlutterPlugin implements Plugin { */ private void configurePlugins() { if (!buildPluginAsAar()) { - getPluginList().each this.&configurePluginProject + getPluginList().each this.&configurePlugin return } - if (useLocalEngine()) { - throw new GradleException("Local engine isn't supported when building the plugins as AAR") - } - List projects = [project] - // Module projects set the `hostProjects` extra property in `include_flutter.groovy`. - // This is required to set the local repository in each host app project. - if (project.ext.has("hostProjects")) { - projects.addAll(project.ext.get("hostProjects")) - } - // Configure the repository for the plugins. - projects.each { hostProject -> - hostProject.repositories { - maven { - url "${getPluginBuildDir()}/outputs/repo" - } + addPluginTasks() + List tasksToExecute = project.gradle.startParameter.taskNames + Set buildTypes = getBuildTypesForTasks(tasksToExecute) + if (tasksToExecute.contains("clean")) { + // Because the plugins are built during configuration, the task "clean" + // cannot run in conjunction with an assembly task. + if (!buildTypes.empty) { + throw new GradleException("Can't run the clean task along with other assemble tasks") } } - getPluginList().each { pluginName, pluginPath -> - configurePluginAar(pluginName, pluginPath, project) + // Build plugins when a task "assembly*" will be called later. + if (!buildTypes.empty) { + // Build the plugin during configuration. + // This is required when Jetifier is enabled, otherwise the implementation dependency + // cannot be added. + buildAarPlugins(buildTypes) } } - private static final Pattern GROUP_PATTERN = ~/group\s+\'(.+)\'/ - private static final Pattern PROJECT_NAME_PATTERN = ~/rootProject\.name\s+=\s+\'(.+)\'/ - - // Adds the plugin AAR dependency to the app project. - private void configurePluginAar(String pluginName, String pluginPath, Project project) { - // Extract the group id from the plugin's build.gradle. - // This is `group ''` - File pluginBuildFile = project.file(Paths.get(pluginPath, "android", "build.gradle")); - if (!pluginBuildFile.exists()) { - throw new GradleException("Plugin $pluginName doesn't have the required file $pluginBuildFile.") - } - - Matcher groupParts = GROUP_PATTERN.matcher(pluginBuildFile.text) - assert groupParts.count == 1 - assert groupParts.hasGroup() - String groupId = groupParts[0][1] - - // Extract the artifact name from the plugin's settings.gradle. - // This is `rootProject.name = ''` - File pluginSettings = project.file(Paths.get(pluginPath, "android", "settings.gradle")); - if (!pluginSettings.exists()) { - throw new GradleException("Plugin $pluginName doesn't have the required file $pluginSettings.") - } - Matcher projectNameParts = PROJECT_NAME_PATTERN.matcher(pluginSettings.text) - assert projectNameParts.count == 1 - assert projectNameParts.hasGroup() - String artifactId = "${projectNameParts[0][1]}_release" - - assert !groupId.empty - project.dependencies.add("api", "$groupId:$artifactId:+") - } - - // Adds the plugin project dependency to the app project . - private void configurePluginProject(String name, String _) { + private void configurePlugin(String name, String _) { Project pluginProject = project.rootProject.findProject(":$name") if (pluginProject == null) { project.logger.error("Plugin project :$name not found. Please update settings.gradle.") @@ -381,6 +343,93 @@ class FlutterPlugin implements Plugin { return androidPlugins } + private void addPluginTasks() { + Properties plugins = getPluginList() + project.android.buildTypes.each { buildType -> + plugins.each { name, path -> + String buildModeValue = buildType.debuggable ? "debug" : "release" + List taskNameParts = ["build", "plugin", buildModeValue] + taskNameParts.addAll(name.split("_")) + String taskName = toCammelCase(taskNameParts) + // Build types can be extended. For example, a build type can extend the `debug` mode. + // In such cases, prevent creating the same task. + if (project.tasks.findByName(taskName) == null) { + project.tasks.create(name: taskName, type: FlutterPluginTask) { + flutterExecutable this.flutterExecutable + buildMode buildModeValue + verbose isVerbose() + pluginDir project.file(path) + sourceDir project.file(project.flutter.source) + intermediateDir getPluginBuildDir() + } + } + } + } + } + + private void buildAarPlugins(Set buildTypes) { + List projects = [project] + // Module projects set the `hostProjects` extra property in `include_flutter.groovy`. + // This is required to set the local repository in each host app project. + if (project.ext.has("hostProjects")) { + projects.addAll(project.ext.get("hostProjects")) + } + projects.each { hostProject -> + hostProject.repositories { + maven { + url "${getPluginBuildDir()}/outputs/repo" + } + } + } + buildTypes.each { buildType -> + project.tasks.withType(FlutterPluginTask).all { pluginTask -> + String buildMode = buildType.debuggable ? "debug" : "release" + if (pluginTask.buildMode != buildMode) { + return + } + pluginTask.execute() + pluginTask.intermediateDir.eachFileRecurse(FILES) { file -> + if (file.name != "maven-metadata.xml") { + return + } + def mavenMetadata = new XmlParser().parse(file) + String groupId = mavenMetadata.groupId.text() + String artifactId = mavenMetadata.artifactId.text() + + if (!artifactId.endsWith(buildMode)) { + return + } + // Add the plugin dependency based on the Maven metadata. + addApiDependencies(project, buildType.name, "$groupId:$artifactId:+@aar", { + transitive = true + }) + } + } + } + } + + /** + * Returns a set with the build type names that apply to the given list of tasks + * required to configure the plugin dependencies. + */ + private Set getBuildTypesForTasks(List tasksToExecute) { + Set buildTypes = [] + tasksToExecute.each { task -> + project.android.buildTypes.each { buildType -> + if (task == "androidDependencies" || task.endsWith("dependencies")) { + // The tasks to query the dependencies includes all the build types. + buildTypes.add(buildType) + } else if (task.endsWith("assemble")) { + // The `assemble` task includes all the build types. + buildTypes.add(buildType) + } else if (task.endsWith(buildType.name.capitalize())) { + buildTypes.add(buildType) + } + } + } + return buildTypes + } + private static String toCammelCase(List parts) { if (parts.empty) { return "" @@ -878,3 +927,56 @@ class FlutterTask extends BaseFlutterTask { buildBundle() } } + +class FlutterPluginTask extends DefaultTask { + File flutterExecutable + @Optional @Input + Boolean verbose + @Input + String buildMode + @Input + File pluginDir + @Input + File intermediateDir + File sourceDir + + @InputFiles + FileCollection getSourceFiles() { + return project.fileTree( + dir: sourceDir, + exclude: ["android", "ios"], + include: ["pubspec.yaml"] + ) + } + + @OutputDirectory + File getOutputDirectory() { + return intermediateDir + } + + @TaskAction + void build() { + intermediateDir.mkdirs() + project.exec { + executable flutterExecutable.absolutePath + workingDir pluginDir + args "build", "aar" + args "--quiet" + args "--suppress-analytics" + args "--output-dir", "${intermediateDir}" + switch (buildMode) { + case 'release': + args "--release" + break + case 'debug': + args "--debug" + break + default: + assert false + } + if (verbose) { + args "--verbose" + } + } + } +} diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index dfeca8a73e..f383329f01 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -22,6 +22,7 @@ import '../base/utils.dart'; import '../base/version.dart'; import '../build_info.dart'; import '../cache.dart'; +import '../features.dart'; import '../flutter_manifest.dart'; import '../globals.dart'; import '../project.dart'; @@ -46,20 +47,18 @@ class GradleUtils { return _cachedExecutable; } - final Map _cachedAppProject = - {}; - /// Gets the [GradleProject] for the [project] if built as an app. - Future getAppProject(FlutterProject project) async { - _cachedAppProject[project] ??= await _readGradleProject(project, isLibrary: false); - return _cachedAppProject[project]; + GradleProject _cachedAppProject; + /// Gets the [GradleProject] for the current [FlutterProject] if built as an app. + Future get appProject async { + _cachedAppProject ??= await _readGradleProject(isLibrary: false); + return _cachedAppProject; } - final Map _cachedLibraryProject = - {}; - /// Gets the [GradleProject] for the [project] if built as a library. - Future getLibraryProject(FlutterProject project) async { - _cachedLibraryProject[project] ??= await _readGradleProject(project, isLibrary: true); - return _cachedLibraryProject[project]; + GradleProject _cachedLibraryProject; + /// Gets the [GradleProject] for the current [FlutterProject] if built as a library. + Future get libraryProject async { + _cachedLibraryProject ??= await _readGradleProject(isLibrary: true); + return _cachedLibraryProject; } } @@ -134,8 +133,7 @@ Future getGradleAppOut(AndroidProject androidProject) async { case FlutterPluginVersion.managed: // Fall through. The managed plugin matches plugin v2 for now. case FlutterPluginVersion.v2: - final GradleProject gradleProject = - await gradleUtils.getAppProject(FlutterProject.current()); + final GradleProject gradleProject = await gradleUtils.appProject; return fs.file(gradleProject.apkDirectory.childFile('app.apk')); } return null; @@ -211,10 +209,8 @@ void createSettingsAarGradle(Directory androidDirectory) { // Note: Dependencies are resolved and possibly downloaded as a side-effect // of calculating the app properties using Gradle. This may take minutes. -Future _readGradleProject( - FlutterProject flutterProject, { - bool isLibrary = false, -}) async { +Future _readGradleProject({bool isLibrary = false}) async { + final FlutterProject flutterProject = FlutterProject.current(); final String gradlew = await gradleUtils.getExecutable(flutterProject); updateLocalProperties(project: flutterProject); @@ -222,6 +218,10 @@ Future _readGradleProject( final FlutterManifest manifest = flutterProject.manifest; final Directory hostAppGradleRoot = flutterProject.android.hostAppGradleRoot; + if (featureFlags.isPluginAsAarEnabled && + !manifest.isPlugin && !manifest.isModule) { + createSettingsAarGradle(hostAppGradleRoot); + } if (manifest.isPlugin) { assert(isLibrary); return GradleProject( @@ -581,9 +581,9 @@ Future buildGradleAar({ GradleProject gradleProject; if (manifest.isModule) { - gradleProject = await gradleUtils.getAppProject(project); + gradleProject = await gradleUtils.appProject; } else if (manifest.isPlugin) { - gradleProject = await gradleUtils.getLibraryProject(project); + gradleProject = await gradleUtils.libraryProject; } else { throwToolExit('AARs can only be built for plugin or module projects.'); } @@ -612,11 +612,13 @@ Future buildGradleAar({ '-Pflutter-root=$flutterRoot', '-Poutput-dir=${gradleProject.buildDirectory}', '-Pis-plugin=${manifest.isPlugin}', + '-Dbuild-plugins-as-aars=true', ]; if (target != null && target.isNotEmpty) { command.add('-Ptarget=$target'); } + if (androidBuildInfo.targetArchs.isNotEmpty) { final String targetPlatforms = androidBuildInfo.targetArchs .map(getPlatformNameForAndroidArch).join(','); @@ -631,30 +633,34 @@ Future buildGradleAar({ command.add(aarTask); final Stopwatch sw = Stopwatch()..start(); - RunResult result; + int exitCode = 1; + try { - result = await processUtils.run( + exitCode = await processUtils.stream( command, workingDirectory: project.android.hostAppGradleRoot.path, allowReentrantFlutter: true, environment: gradleEnv, + mapFunction: (String line) { + // Always print the full line in verbose mode. + if (logger.isVerbose) { + return line; + } + return null; + }, ); } finally { status.stop(); } - flutterUsage.sendTiming('build', 'gradle-aar', sw.elapsed); + flutterUsage.sendTiming('build', 'gradle-aar', Duration(milliseconds: sw.elapsedMilliseconds)); - if (result.exitCode != 0) { - printStatus(result.stdout, wrap: false); - printError(result.stderr, wrap: false); - throwToolExit('Gradle task $aarTask failed with exit code $exitCode.', exitCode: exitCode); + if (exitCode != 0) { + throwToolExit('Gradle task $aarTask failed with exit code $exitCode', exitCode: exitCode); } final Directory repoDirectory = gradleProject.repoDirectory; if (!repoDirectory.existsSync()) { - printStatus(result.stdout, wrap: false); - printError(result.stderr, wrap: false); - throwToolExit('Gradle task $aarTask failed to produce $repoDirectory.', exitCode: exitCode); + throwToolExit('Gradle task $aarTask failed to produce $repoDirectory', exitCode: exitCode); } printStatus('Built ${fs.path.relative(repoDirectory.path)}.', color: TerminalColor.green); } @@ -724,33 +730,21 @@ Future _buildGradleProjectV2( FlutterProject flutterProject, AndroidBuildInfo androidBuildInfo, String target, - bool isBuildingBundle, { - bool shouldBuildPluginAsAar = false, -}) async { + bool isBuildingBundle, +) async { final String gradlew = await gradleUtils.getExecutable(flutterProject); - final GradleProject gradleProject = await gradleUtils.getAppProject(flutterProject); - - if (shouldBuildPluginAsAar) { - // Create a settings.gradle that doesn't import the plugins as subprojects. - createSettingsAarGradle(flutterProject.android.hostAppGradleRoot); - await buildPluginsAsAar( - flutterProject, - androidBuildInfo, - buildDirectory: gradleProject.buildDirectory, - ); - } - + final GradleProject project = await gradleUtils.appProject; final BuildInfo buildInfo = androidBuildInfo.buildInfo; String assembleTask; if (isBuildingBundle) { - assembleTask = gradleProject.bundleTaskFor(buildInfo); + assembleTask = project.bundleTaskFor(buildInfo); } else { - assembleTask = gradleProject.assembleTaskFor(buildInfo); + assembleTask = project.assembleTaskFor(buildInfo); } if (assembleTask == null) { - printUndefinedTask(gradleProject, buildInfo); + printUndefinedTask(project, buildInfo); throwToolExit('Gradle build aborted.'); } final Status status = logger.startProgress( @@ -797,12 +791,13 @@ Future _buildGradleProjectV2( .map(getPlatformNameForAndroidArch).join(','); command.add('-Ptarget-platform=$targetPlatforms'); } - if (shouldBuildPluginAsAar) { + if (featureFlags.isPluginAsAarEnabled) { // 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 (!flutterProject.manifest.isModule) { + command.add('--settings-file=settings_aar.gradle'); + } } command.add(assembleTask); bool potentialAndroidXFailure = false; @@ -849,60 +844,24 @@ Future _buildGradleProjectV2( printStatus('To learn more, see: https://developer.android.com/studio/build/shrink-code', indent: 4); BuildEvent('r8-failure').send(); } else if (potentialAndroidXFailure) { - final bool hasPlugins = flutterProject.flutterPluginsFile.existsSync(); - final bool usesAndroidX = isAppUsingAndroidX(flutterProject.android.hostAppGradleRoot); - if (!hasPlugins) { - // If the app doesn't use any plugin, then it's unclear where the incompatibility is coming from. - BuildEvent('android-x-failure', eventError: 'app-not-using-plugins').send(); - } - if (hasPlugins && !usesAndroidX) { - // If the app isn't using AndroidX, then the app is likely using a plugin already migrated to AndroidX. - printStatus('AndroidX incompatibilities may have caused this build to fail. '); - printStatus('Please migrate your app to AndroidX. See https://goo.gl/CP92wY.'); - BuildEvent('android-x-failure', eventError: 'app-not-using-androidx').send(); - } - if (hasPlugins && usesAndroidX && shouldBuildPluginAsAar) { - // This is a dependency conflict instead of an AndroidX failure since by this point - // the app is using AndroidX, the plugins are built as AARs, Jetifier translated - // Support libraries for AndroidX equivalents. - BuildEvent('android-x-failure', eventError: 'using-jetifier').send(); - } - if (hasPlugins && usesAndroidX && !shouldBuildPluginAsAar) { - printStatus( - 'The built failed likely due to AndroidX incompatibilities in a plugin. ' - 'The tool is about to try using Jetfier to solve the incompatibility.' - ); - BuildEvent('android-x-failure', eventError: 'not-using-jetifier').send(); - // The app is using Androidx, but Jetifier hasn't run yet. - // Call the current method again, build the plugins as AAR, so Jetifier can translate - // the dependencies. - // NOTE: Don't build the plugins as AARs by default since this drastically increases - // the build time. - await _buildGradleProjectV2( - flutterProject, - androidBuildInfo, - target, - isBuildingBundle, - shouldBuildPluginAsAar: true, - ); - return; - } + printStatus('AndroidX incompatibilities may have caused this build to fail. See https://goo.gl/CP92wY.'); + BuildEvent('android-x-failure').send(); } throwToolExit('Gradle task $assembleTask failed with exit code $exitCode', exitCode: exitCode); } flutterUsage.sendTiming('build', 'gradle-v2', Duration(milliseconds: sw.elapsedMilliseconds)); if (!isBuildingBundle) { - final Iterable apkFiles = findApkFiles(gradleProject, androidBuildInfo); + final Iterable apkFiles = findApkFiles(project, androidBuildInfo); if (apkFiles.isEmpty) { throwToolExit('Gradle build failed to produce an Android package.'); } // Copy the first APK to app.apk, so `flutter run`, `flutter install`, etc. can find it. // TODO(blasten): Handle multiple APKs. - apkFiles.first.copySync(gradleProject.apkDirectory.childFile('app.apk').path); + apkFiles.first.copySync(project.apkDirectory.childFile('app.apk').path); - printTrace('calculateSha: ${gradleProject.apkDirectory}/app.apk'); - final File apkShaFile = gradleProject.apkDirectory.childFile('app.apk.sha1'); + printTrace('calculateSha: ${project.apkDirectory}/app.apk'); + final File apkShaFile = project.apkDirectory.childFile('app.apk.sha1'); apkShaFile.writeAsStringSync(_calculateSha(apkFiles.first)); for (File apkFile in apkFiles) { @@ -916,7 +875,7 @@ Future _buildGradleProjectV2( color: TerminalColor.green); } } else { - final File bundleFile = findBundleFile(gradleProject, buildInfo); + final File bundleFile = findBundleFile(project, buildInfo); if (bundleFile == null) { throwToolExit('Gradle build failed to produce an Android bundle package.'); } @@ -932,61 +891,6 @@ Future _buildGradleProjectV2( } } -/// Returns [true] if the current app uses AndroidX. -// TODO(egarciad): https://github.com/flutter/flutter/issues/40800 -// Remove `FlutterManifest.usesAndroidX` and provide a unified `AndroidProject.usesAndroidX`. -@visibleForTesting -bool isAppUsingAndroidX(Directory androidDirectory) { - final File properties = androidDirectory.childFile('gradle.properties'); - if (!properties.existsSync()) { - return false; - } - return properties.readAsStringSync().contains('android.useAndroidX=true'); -} - -/// Builds the plugins as AARs. -@visibleForTesting -Future buildPluginsAsAar( - FlutterProject flutterProject, - AndroidBuildInfo androidBuildInfo, { - String buildDirectory, -}) async { - final File flutterPluginFile = flutterProject.flutterPluginsFile; - if (!flutterPluginFile.existsSync()) { - return; - } - final List plugins = flutterPluginFile.readAsStringSync().split('\n'); - for (String plugin in plugins) { - final List pluginParts = plugin.split('='); - if (pluginParts.length != 2) { - continue; - } - final Directory pluginDirectory = fs.directory(pluginParts.last); - assert(pluginDirectory.existsSync()); - - final String pluginName = pluginParts.first; - logger.printStatus('Building plugin $pluginName...'); - try { - await buildGradleAar( - project: FlutterProject.fromDirectory(pluginDirectory), - androidBuildInfo: const AndroidBuildInfo( - BuildInfo( - BuildMode.release, // Plugins are built as release. - null, // Plugins don't define flavors. - ), - ), - target: '', - outputDir: buildDirectory, - ); - } on ToolExit { - // Log the entire plugin entry in `.flutter-plugins` since it - // includes the plugin name and the version. - BuildEvent('plugin-aar-failure', eventError: plugin).send(); - throwToolExit('The plugin $pluginName could not be built due to the issue above. '); - } - } -} - @visibleForTesting Iterable findApkFiles(GradleProject project, AndroidBuildInfo androidBuildInfo) { final Iterable apkFileNames = project.apkFilesFor(androidBuildInfo); diff --git a/packages/flutter_tools/lib/src/features.dart b/packages/flutter_tools/lib/src/features.dart index 170126739e..cdb13138af 100644 --- a/packages/flutter_tools/lib/src/features.dart +++ b/packages/flutter_tools/lib/src/features.dart @@ -36,6 +36,9 @@ class FeatureFlags { /// Whether flutter desktop for Windows is enabled. bool get isWindowsEnabled => _isEnabled(flutterWindowsDesktopFeature); + /// Whether plugins are built as AARs in app projects. + bool get isPluginAsAarEnabled => _isEnabled(flutterBuildPluginAsAarFeature); + // Calculate whether a particular feature is enabled for the current channel. static bool _isEnabled(Feature feature) { final String currentChannel = FlutterVersion.instance.channel; diff --git a/packages/flutter_tools/lib/src/reporting/events.dart b/packages/flutter_tools/lib/src/reporting/events.dart index 99def6b821..8a73f51636 100644 --- a/packages/flutter_tools/lib/src/reporting/events.dart +++ b/packages/flutter_tools/lib/src/reporting/events.dart @@ -125,7 +125,6 @@ class BuildEvent extends UsageEvent { BuildEvent(String parameter, { this.command, this.settings, - this.eventError, }) : super( 'build' + (FlutterCommand.current == null ? '' : '-${FlutterCommand.current.name}'), @@ -133,7 +132,6 @@ class BuildEvent extends UsageEvent { final String command; final String settings; - final String eventError; @override void send() { @@ -142,8 +140,6 @@ class BuildEvent extends UsageEvent { CustomDimensions.buildEventCommand: command, if (settings != null) CustomDimensions.buildEventSettings: settings, - if (eventError != null) - CustomDimensions.buildEventError: eventError, }); flutterUsage.sendEvent(category, parameter, parameters: parameters); } diff --git a/packages/flutter_tools/lib/src/reporting/usage.dart b/packages/flutter_tools/lib/src/reporting/usage.dart index a3d225d9d2..0f33c97571 100644 --- a/packages/flutter_tools/lib/src/reporting/usage.dart +++ b/packages/flutter_tools/lib/src/reporting/usage.dart @@ -53,7 +53,6 @@ enum CustomDimensions { commandBuildApkSplitPerAbi, // cd40 commandBuildAppBundleTargetPlatform, // cd41 commandBuildAppBundleBuildMode, // cd42 - buildEventError, // cd43 } String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}'; 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 4450e6dd89..1ab626d65d 100644 --- a/packages/flutter_tools/test/general.shard/android/gradle_test.dart +++ b/packages/flutter_tools/test/general.shard/android/gradle_test.dart @@ -25,6 +25,7 @@ import 'package:process/process.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/mocks.dart'; import '../../src/pubspec_schema.dart'; void main() { @@ -1154,153 +1155,6 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; }); }); - group('isAppUsingAndroidX', () { - FileSystem fs; - - setUp(() { - fs = MemoryFileSystem(); - }); - - testUsingContext('returns true when the project is using AndroidX', () async { - final Directory androidDirectory = fs.systemTempDirectory.createTempSync('android.'); - - androidDirectory - .childFile('gradle.properties') - .writeAsStringSync('android.useAndroidX=true'); - - expect(isAppUsingAndroidX(androidDirectory), isTrue); - - }, overrides: { - FileSystem: () => fs, - }); - - testUsingContext('returns false when the project is not using AndroidX', () async { - final Directory androidDirectory = fs.systemTempDirectory.createTempSync('android.'); - - androidDirectory - .childFile('gradle.properties') - .writeAsStringSync('android.useAndroidX=false'); - - expect(isAppUsingAndroidX(androidDirectory), isFalse); - - }, overrides: { - FileSystem: () => fs, - }); - - testUsingContext('returns false when gradle.properties does not exist', () async { - final Directory androidDirectory = fs.systemTempDirectory.createTempSync('android.'); - - expect(isAppUsingAndroidX(androidDirectory), isFalse); - - }, overrides: { - FileSystem: () => fs, - }); - }); - - group('buildPluginsAsAar', () { - FileSystem fs; - MockProcessManager mockProcessManager; - MockAndroidSdk mockAndroidSdk; - - setUp(() { - fs = MemoryFileSystem(); - - mockProcessManager = MockProcessManager(); - when(mockProcessManager.run( - any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'), - )).thenAnswer((_) async => ProcessResult(1, 0, '', '')); - - mockAndroidSdk = MockAndroidSdk(); - when(mockAndroidSdk.directory).thenReturn('irrelevant'); - }); - - testUsingContext('calls gradle', () async { - final Directory androidDirectory = fs.directory('android.'); - androidDirectory.createSync(); - androidDirectory - .childFile('pubspec.yaml') - .writeAsStringSync('name: irrelevant'); - - final Directory plugin1 = fs.directory('plugin1.'); - plugin1 - ..createSync() - ..childFile('pubspec.yaml') - .writeAsStringSync(''' -name: irrelevant -flutter: - plugin: - androidPackage: irrelevant -'''); - final Directory plugin2 = fs.directory('plugin2.'); - plugin2 - ..createSync() - ..childFile('pubspec.yaml') - .writeAsStringSync(''' -name: irrelevant -flutter: - plugin: - androidPackage: irrelevant -'''); - - androidDirectory - .childFile('.flutter-plugins') - .writeAsStringSync(''' -plugin1=${plugin1.path} -plugin2=${plugin2.path} -'''); - final Directory buildDirectory = androidDirectory.childDirectory('build'); - buildDirectory - .childDirectory('outputs') - .childDirectory('repo') - .createSync(recursive: true); - - await buildPluginsAsAar( - FlutterProject.fromPath(androidDirectory.path), - const AndroidBuildInfo(BuildInfo.release), - buildDirectory: buildDirectory.path, - ); - - final String flutterRoot = fs.path.absolute(Cache.flutterRoot); - final String initScript = fs.path.join(flutterRoot, 'packages', - 'flutter_tools', 'gradle', 'aar_init_script.gradle'); - verify(mockProcessManager.run( - [ - 'gradlew', - '-I=$initScript', - '-Pflutter-root=$flutterRoot', - '-Poutput-dir=${buildDirectory.path}', - '-Pis-plugin=true', - '-Ptarget-platform=android-arm,android-arm64', - 'assembleAarRelease', - ], - environment: anyNamed('environment'), - workingDirectory: plugin1.childDirectory('android').path), - ).called(1); - - verify(mockProcessManager.run( - [ - 'gradlew', - '-I=$initScript', - '-Pflutter-root=$flutterRoot', - '-Poutput-dir=${buildDirectory.path}', - '-Pis-plugin=true', - '-Ptarget-platform=android-arm,android-arm64', - 'assembleAarRelease', - ], - environment: anyNamed('environment'), - workingDirectory: plugin2.childDirectory('android').path), - ).called(1); - - }, overrides: { - AndroidSdk: () => mockAndroidSdk, - FileSystem: () => fs, - GradleUtils: () => FakeGradleUtils(), - ProcessManager: () => mockProcessManager, - }); - }); - group('gradle build', () { MockAndroidSdk mockAndroidSdk; MockAndroidStudio mockAndroidStudio; @@ -1381,13 +1235,11 @@ plugin2=${plugin2.path} fs.currentDirectory = 'path/to/project'; // Let any process start. Assert after. - when(mockProcessManager.run( + when(mockProcessManager.start( any, environment: anyNamed('environment'), workingDirectory: anyNamed('workingDirectory')) - ).thenAnswer( - (_) async => ProcessResult(1, 0, '', ''), - ); + ).thenAnswer((Invocation invocation) => Future.value(MockProcess())); fs.directory('build/outputs/repo').createSync(recursive: true); await buildGradleAar( @@ -1397,11 +1249,11 @@ plugin2=${plugin2.path} target: '' ); - final List actualGradlewCall = verify(mockProcessManager.run( + final List actualGradlewCall = verify(mockProcessManager.start( captureAny, environment: anyNamed('environment'), workingDirectory: anyNamed('workingDirectory')), - ).captured.last; + ).captured.single; expect(actualGradlewCall, contains('/path/to/project/.android/gradlew')); expect(actualGradlewCall, contains('-PlocalEngineOut=out/android_arm')); @@ -1432,14 +1284,6 @@ Platform fakePlatform(String name) { return FakePlatform.fromPlatform(const LocalPlatform())..operatingSystem = name; } -class FakeGradleUtils extends GradleUtils { - @override - Future getExecutable(FlutterProject project) async { - return 'gradlew'; - } -} - -class MockAndroidSdk extends Mock implements AndroidSdk {} class MockAndroidStudio extends Mock implements AndroidStudio {} class MockDirectory extends Mock implements Directory {} class MockFile extends Mock implements File {} diff --git a/packages/flutter_tools/test/general.shard/features_test.dart b/packages/flutter_tools/test/general.shard/features_test.dart index a7c978efd6..3594a6e211 100644 --- a/packages/flutter_tools/test/general.shard/features_test.dart +++ b/packages/flutter_tools/test/general.shard/features_test.dart @@ -432,6 +432,21 @@ void main() { expect(featureFlags.isWindowsEnabled, false); })); + + /// Plugins as AARS + test('plugins built as AARs with config on master', () => testbed.run(() { + when(mockFlutterVerion.channel).thenReturn('master'); + when(mockFlutterConfig.getValue('enable-build-plugin-as-aar')).thenReturn(false); + + expect(featureFlags.isPluginAsAarEnabled, false); + })); + + test('plugins built as AARs with config on dev', () => testbed.run(() { + when(mockFlutterVerion.channel).thenReturn('dev'); + when(mockFlutterConfig.getValue('enable-build-plugin-as-aar')).thenReturn(false); + + expect(featureFlags.isPluginAsAarEnabled, false); + })); }); } diff --git a/packages/flutter_tools/test/src/testbed.dart b/packages/flutter_tools/test/src/testbed.dart index c73a808e2d..446d111719 100644 --- a/packages/flutter_tools/test/src/testbed.dart +++ b/packages/flutter_tools/test/src/testbed.dart @@ -693,6 +693,7 @@ class TestFeatureFlags implements FeatureFlags { this.isMacOSEnabled = false, this.isWebEnabled = false, this.isWindowsEnabled = false, + this.isPluginAsAarEnabled = false, }); @override @@ -706,4 +707,7 @@ class TestFeatureFlags implements FeatureFlags { @override final bool isWindowsEnabled; + + @override + final bool isPluginAsAarEnabled; }