Fix plugin java class desugar (#73758)
This commit is contained in:
parent
49667ba2f5
commit
da90156f8c
44
dev/devicelab/bin/tasks/gradle_desugar_classes_test.dart
Normal file
44
dev/devicelab/bin/tasks/gradle_desugar_classes_test.dart
Normal file
@ -0,0 +1,44 @@
|
||||
// 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)) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user