Unverified Commit da90156f authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Fix plugin java class desugar (#73758)

parent 49667ba2
// 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.
import 'dart:io';
import 'package:flutter_devicelab/framework/apk_utils.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/task_result.dart';
import 'package:flutter_devicelab/framework/utils.dart';
Future<void> main() async {
await task(() async {
try {
await runProjectTest((FlutterProject flutterProject) async {
section('APK contains plugin classes');
flutterProject.addPlugin('google_maps_flutter', value: '^1.0.10');
await inDirectory(flutterProject.rootPath, () async {
await flutter('build', options: <String>[
'apk',
'--debug',
'--target-platform=android-arm',
]);
final File apk = File('${flutterProject.rootPath}/build/app/outputs/flutter-apk/app-debug.apk');
if (!apk.existsSync()) {
throw TaskResult.failure('Expected ${apk.path} to exist, but it doesn\'t');
}
// https://github.com/flutter/flutter/issues/72185
await checkApkContainsMethods(apk, <String>[
'io.flutter.plugins.googlemaps.GoogleMapController void onFlutterViewAttached(android.view.View)',
'io.flutter.plugins.googlemaps.GoogleMapController void onFlutterViewDetached()',
]);
});
});
return TaskResult.success(null);
} on TaskResult catch (taskResult) {
return taskResult;
} catch (e) {
return TaskResult.failure(e.toString());
}
});
}
......@@ -115,12 +115,14 @@ String get _androidHome {
/// Executes an APK analyzer subcommand.
Future<String> _evalApkAnalyzer(
List<String> args, {
bool printStdout = true,
bool printStdout = false,
String workingDirectory,
}) async {
final String javaHome = await findJavaHome();
final String apkAnalyzer = path
if (javaHome == null || javaHome.isEmpty) {
throw Exception('No JAVA_HOME set.');
}
final String apkAnalyzer = path
.join(_androidHome, 'cmdline-tools', 'latest', 'bin', Platform.isWindows ? 'apkanalyzer.bat' : 'apkanalyzer');
if (canRun(apkAnalyzer)) {
return eval(
......@@ -165,6 +167,7 @@ class ApkExtractor {
bool _extracted = false;
Set<String> _classes = const <String>{};
Set<String> _methods = const <String>{};
Future<void> _extractDex() async {
if (_extracted) {
......@@ -177,22 +180,17 @@ class ApkExtractor {
apkFile.path,
],
);
final List<String> lines = packages.split('\n');
_classes = Set<String>.from(
packages
.split('\n')
.where((String line) => line.startsWith('C'))
.map<String>((String line) => line.split('\t').last),
lines.where((String line) => line.startsWith('C'))
.map<String>((String line) => line.split('\t').last),
);
assert(_classes.isNotEmpty);
_extracted = true;
}
// Removes any temporary directory.
void dispose() {
if (!_extracted) {
return;
}
_classes = const <String>{};
_methods = Set<String>.from(
lines.where((String line) => line.startsWith('M'))
.map<String>((String line) => line.split('\t').last)
);
assert(_methods.isNotEmpty);
_extracted = true;
}
......@@ -201,6 +199,13 @@ class ApkExtractor {
await _extractDex();
return _classes.contains(className);
}
/// Returns true if the APK contains a given method.
/// For example: io.flutter.plugins.googlemaps.GoogleMapController void onFlutterViewAttached(android.view.View)
Future<bool> containsMethod(String methodName) async {
await _extractDex();
return _methods.contains(methodName);
}
}
/// Gets the content of the `AndroidManifest.xml`.
......@@ -223,7 +228,16 @@ Future<void> checkApkContainsClasses(File apk, List<String> classes) async {
throw Exception("APK doesn't contain class `$className`.");
}
}
extractor.dispose();
}
/// Checks that the methods are defined in the APK, throws otherwise.
Future<void> checkApkContainsMethods(File apk, List<String> methods) async {
final ApkExtractor extractor = ApkExtractor(apk);
for (final String method in methods) {
if (!(await extractor.containsMethod(method))) {
throw Exception("APK doesn't contain method `$method`.");
}
}
}
class FlutterProject {
......@@ -288,7 +302,7 @@ subprojects {
String content = await pubspec.readAsString();
content = content.replaceFirst(
'${platformLineSep}dependencies:$platformLineSep',
'${platformLineSep}dependencies:$platformLineSep $plugin:$value$platformLineSep',
'${platformLineSep}dependencies:$platformLineSep $plugin: $value$platformLineSep',
);
await pubspec.writeAsString(content, flush: true);
}
......
......@@ -456,6 +456,8 @@ List<String> flutterCommandArgs(String command, List<String> options) {
];
}
/// Runs the flutter `command`, and returns the exit code.
/// If `canFail` is `false`, the future completes with an error.
Future<int> flutter(String command, {
List<String> options = const <String>[],
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
......
......@@ -101,6 +101,19 @@ class FlutterPlugin implements Plugin<Project> {
void apply(Project project) {
this.project = project
// Configure the Maven repository.
String hostedRepository = System.env.FLUTTER_STORAGE_BASE_URL ?: DEFAULT_MAVEN_HOST
String repository = useLocalEngine()
? project.property('local-engine-repo')
: "$hostedRepository/download.flutter.io"
project.rootProject.allprojects {
repositories {
maven {
url repository
}
}
}
project.extensions.create("flutter", FlutterExtension)
this.addFlutterTasks(project)
......@@ -184,8 +197,7 @@ class FlutterPlugin implements Plugin<Project> {
localEngine = engineOut.name
localEngineSrcPath = engineOut.parentFile.parent
}
project.android.buildTypes.each this.&addFlutterDependencies
project.android.buildTypes.whenObjectAdded this.&addFlutterDependencies
project.android.buildTypes.all this.&addFlutterDependencies
}
/**
......@@ -199,21 +211,16 @@ class FlutterPlugin implements Plugin<Project> {
if (!supportsBuildMode(flutterBuildMode)) {
return
}
String hostedRepository = System.env.FLUTTER_STORAGE_BASE_URL ?: DEFAULT_MAVEN_HOST
String repository = useLocalEngine()
? project.property('local-engine-repo')
: "$hostedRepository/download.flutter.io"
project.rootProject.allprojects {
repositories {
maven {
url repository
}
}
// The embedding is set as an API dependency in a Flutter plugin.
// Therefore, don't make the app project depend on the embedding if there are Flutter
// plugins.
// This prevents duplicated classes when using custom build types. That is, a custom build
// type like profile is used, and the plugin and app projects have API dependencies on the
// embedding.
if (!isFlutterAppProject() || getPluginList().size() == 0) {
addApiDependencies(project, buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion")
}
// Add the embedding dependency.
addApiDependencies(project, buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion")
List<String> platforms = getTargetPlatforms().collect()
// Debug mode includes x86 and x64, which are commonly used in emulators.
if (flutterBuildMode == "debug" && !useLocalEngine()) {
......@@ -303,7 +310,7 @@ class FlutterPlugin implements Plugin<Project> {
}
// Add plugin dependency to the app project.
project.dependencies {
implementation pluginProject
api pluginProject
}
Closure addEmbeddingDependencyToPlugin = { buildType ->
String flutterBuildMode = buildModeFor(buildType)
......@@ -318,39 +325,23 @@ class FlutterPlugin implements Plugin<Project> {
pluginProject.android.buildTypes {
"${buildType.name}" {}
}
// The embedding is a compileOnly dependency of a Flutter plugin,
// however prior to Gradle 6.0.0, it must be an API dependency.
// The embedding is API dependency of the plugin, so the AGP is able to desugar
// default method implementations when the interface is implemented by a plugin.
//
// Not doing so, causes transitive dependency resolution conflicts.
// That is, the embedding dependencies resolved in the plugin are
// different than the ones resolved in the app.
if (isGradleVersionGraterOrEqualThan('6.0.0')) {
addCompileOnlyDependency(
pluginProject,
buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion"
)
} else {
addApiDependencies(
pluginProject,
buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion"
)
}
// See https://issuetracker.google.com/139821726, and
// https://github.com/flutter/flutter/issues/72185 for more details.
addApiDependencies(
pluginProject,
buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion"
)
}
// Wait until the Android plugin loaded.
pluginProject.afterEvaluate {
project.android.buildTypes.each addEmbeddingDependencyToPlugin
project.android.buildTypes.whenObjectAdded addEmbeddingDependencyToPlugin
project.android.buildTypes.all addEmbeddingDependencyToPlugin
}
}
// Returns `true` if the current Gradle version is greater or equal to the given version.
private isGradleVersionGraterOrEqualThan(String version) {
return VersionNumber.parse(project.gradle.gradleVersion)
.compareTo(VersionNumber.parse(version)) >= 0
}
// Returns `true` if the given path contains an `android/build.gradle` file.
//
// TODO(egarciad): Fix https://github.com/flutter/flutter/issues/39657.
......@@ -659,6 +650,10 @@ class FlutterPlugin implements Plugin<Project> {
return variant.hasProperty("assembleProvider") ? variant.assembleProvider.get() : variant.assemble
}
private boolean isFlutterAppProject() {
return project.android.hasProperty("applicationVariants")
}
private void addFlutterTasks(Project project) {
if (project.state.failure) {
return
......@@ -816,8 +811,7 @@ class FlutterPlugin implements Plugin<Project> {
}
return copyFlutterAssetsTask
}
boolean isFlutterAppProject = project.android.hasProperty("applicationVariants")
if (isFlutterAppProject) {
if (isFlutterAppProject()) {
project.android.applicationVariants.all { variant ->
Task assembleTask = getAssembleTask(variant)
if (!shouldConfigureFlutterTask(assembleTask)) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment