Unverified Commit 965397cf authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Fix how Gradle resolves Android plugin (#97823)

parent c659ad6a
...@@ -20,6 +20,7 @@ assert object instanceof Map ...@@ -20,6 +20,7 @@ assert object instanceof Map
assert object.plugins instanceof Map assert object.plugins instanceof Map
assert object.plugins.android instanceof List assert object.plugins.android instanceof List
// Includes the Flutter plugins that support the Android platform. // Includes the Flutter plugins that support the Android platform.
// This logic must be kept in sync with the logic in flutter.gradle.
object.plugins.android.each { androidPlugin -> object.plugins.android.each { androidPlugin ->
assert androidPlugin.name instanceof String assert androidPlugin.name instanceof String
assert androidPlugin.path instanceof String assert androidPlugin.path instanceof String
......
...@@ -48,7 +48,7 @@ class FlutterExtension { ...@@ -48,7 +48,7 @@ class FlutterExtension {
* Specifies the relative directory to the Flutter project directory. * Specifies the relative directory to the Flutter project directory.
* In an app project, this is ../.. since the app's build.gradle is under android/app. * In an app project, this is ../.. since the app's build.gradle is under android/app.
*/ */
String source String source = '../..'
/** Allows to override the target file. Otherwise, the target is lib/main.dart. */ /** Allows to override the target file. Otherwise, the target is lib/main.dart. */
String target String target
...@@ -418,7 +418,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -418,7 +418,7 @@ class FlutterPlugin implements Plugin<Project> {
/** /**
* Compares semantic versions ignoring labels. * Compares semantic versions ignoring labels.
* *
* If the versions are equal (ignoring labels), returns one of the two strings arbitrarily. * If the versions are equal (ignoring labels), returns one of the two strings arbitrarily.
* *
* If minor or patch are omitted (non-conformant to semantic versioning), they are considered zero. * If minor or patch are omitted (non-conformant to semantic versioning), they are considered zero.
...@@ -459,6 +459,9 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -459,6 +459,9 @@ class FlutterPlugin implements Plugin<Project> {
getPluginList().each { plugin -> getPluginList().each { plugin ->
Project pluginProject = project.rootProject.findProject(plugin.key) Project pluginProject = project.rootProject.findProject(plugin.key)
if (pluginProject == null) {
return
}
pluginProject.afterEvaluate { pluginProject.afterEvaluate {
int pluginCompileSdkVersion = pluginProject.android.compileSdkVersion.substring(8) as int int pluginCompileSdkVersion = pluginProject.android.compileSdkVersion.substring(8) as int
maxPluginCompileSdkVersion = Math.max(pluginCompileSdkVersion, maxPluginCompileSdkVersion) maxPluginCompileSdkVersion = Math.max(pluginCompileSdkVersion, maxPluginCompileSdkVersion)
...@@ -476,15 +479,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -476,15 +479,7 @@ class FlutterPlugin implements Plugin<Project> {
} }
} }
} }
} }
}
/**
* Returns `true` if the given path contains an `android/build.gradle` file.
*/
private Boolean doesSupportAndroidPlatform(String path) {
File editableAndroidProject = new File(path, 'android' + File.separator + 'build.gradle')
return editableAndroidProject.exists()
} }
/** /**
...@@ -495,8 +490,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -495,8 +490,7 @@ class FlutterPlugin implements Plugin<Project> {
private void configurePluginDependencies(Object dependencyObject) { private void configurePluginDependencies(Object dependencyObject) {
assert dependencyObject.name instanceof String assert dependencyObject.name instanceof String
Project pluginProject = project.rootProject.findProject(":${dependencyObject.name}") Project pluginProject = project.rootProject.findProject(":${dependencyObject.name}")
if (pluginProject == null || if (pluginProject == null) {
!doesSupportAndroidPlatform(pluginProject.projectDir.parentFile.path)) {
return return
} }
assert dependencyObject.dependencies instanceof List assert dependencyObject.dependencies instanceof List
...@@ -506,8 +500,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -506,8 +500,7 @@ class FlutterPlugin implements Plugin<Project> {
return return
} }
Project dependencyProject = project.rootProject.findProject(":$pluginDependencyName") Project dependencyProject = project.rootProject.findProject(":$pluginDependencyName")
if (dependencyProject == null || if (dependencyProject == null) {
!doesSupportAndroidPlatform(dependencyProject.projectDir.parentFile.path)) {
return return
} }
// Wait for the Android plugin to load and add the dependency to the plugin project. // Wait for the Android plugin to load and add the dependency to the plugin project.
...@@ -519,17 +512,27 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -519,17 +512,27 @@ class FlutterPlugin implements Plugin<Project> {
} }
} }
/** Gets the list of plugins that support the Android platform. */
private Properties getPluginList() { private Properties getPluginList() {
File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins') Map meta = getDependenciesMetadata()
Properties allPlugins = readPropertiesIfExist(pluginsFile)
Properties androidPlugins = new Properties() Properties androidPlugins = new Properties()
allPlugins.each { name, path -> if (meta == null) {
if (doesSupportAndroidPlatform(path)) { return androidPlugins
androidPlugins.setProperty(name, path) }
assert meta.plugins instanceof Map
assert meta.plugins.android instanceof List
// This logic must be kept in sync with the logic in app_plugin_loader.gradle.
meta.plugins.android.each { androidPlugin ->
assert androidPlugin.name instanceof String
assert androidPlugin.path instanceof String
// Skip plugins that have no native build (such as a Dart-only implementation
// of a federated plugin).
def needsBuild = androidPlugin.containsKey('native_build') ? androidPlugin['native_build'] : true
if (!needsBuild) {
return
} }
// TODO(amirh): log an error if this plugin was specified to be an Android androidPlugins.setProperty(androidPlugin.name, androidPlugin.path)
// plugin according to the new schema, and was missing a build.gradle file.
// https://github.com/flutter/flutter/issues/40784
} }
return androidPlugins return androidPlugins
} }
...@@ -557,14 +560,31 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -557,14 +560,31 @@ class FlutterPlugin implements Plugin<Project> {
// This means, `plugin-a` depends on `plugin-b` and `plugin-c`. // This means, `plugin-a` depends on `plugin-b` and `plugin-c`.
// `plugin-b` depends on `plugin-c`. // `plugin-b` depends on `plugin-c`.
// `plugin-c` doesn't depend on anything. // `plugin-c` doesn't depend on anything.
File pluginsDependencyFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins-dependencies') Map meta = getDependenciesMetadata()
if (meta == null) {
return []
}
assert meta.dependencyGraph instanceof List
return meta.dependencyGraph
}
private Map parsedFlutterPluginsDependencies
/**
* Parses <project-src>/.flutter-plugins-dependencies
*/
private Map getDependenciesMetadata() {
if (parsedFlutterPluginsDependencies) {
return parsedFlutterPluginsDependencies
}
File pluginsDependencyFile = new File(getFlutterSourceDirectory(), '.flutter-plugins-dependencies')
if (pluginsDependencyFile.exists()) { if (pluginsDependencyFile.exists()) {
def object = new JsonSlurper().parseText(pluginsDependencyFile.text) def object = new JsonSlurper().parseText(pluginsDependencyFile.text)
assert object instanceof Map assert object instanceof Map
assert object.dependencyGraph instanceof List parsedFlutterPluginsDependencies = object
return object.dependencyGraph return object
} }
return [] return null
} }
private static String toCammelCase(List<String> parts) { private static String toCammelCase(List<String> parts) {
......
// 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 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/cache.dart';
import '../src/common.dart';
import 'test_utils.dart';
void main() {
late Directory tempDir;
setUp(() {
Cache.flutterRoot = getFlutterRoot();
tempDir = createResolvedTempDirectorySync('flutter_plugin_test.');
});
tearDown(() async {
tryToDelete(tempDir);
});
// Regression test for https://github.com/flutter/flutter/issues/97729.
test('skip plugin if it does not support the Android platform', () async {
final String flutterBin = fileSystem.path.join(
getFlutterRoot(),
'bin',
'flutter',
);
// Create dummy plugin that *only* supports iOS.
processManager.runSync(<String>[
flutterBin,
...getLocalEngineArguments(),
'create',
'--template=plugin',
'--platforms=ios',
'test_plugin',
], workingDirectory: tempDir.path);
final Directory pluginAppDir = tempDir.childDirectory('test_plugin');
// Create an android directory and a build.gradle file within.
final File pluginGradleFile = pluginAppDir
.childDirectory('android')
.childFile('build.gradle')
..createSync(recursive: true);
expect(pluginGradleFile, exists);
pluginGradleFile.writeAsStringSync(r'''
buildscript {
ext.kotlin_version = '1.5.31'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
configurations.classpath {
resolutionStrategy.activateDependencyLocking()
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
''');
final Directory pluginExampleAppDir =
pluginAppDir.childDirectory('example');
// Add android support to the plugin's example app.
final ProcessResult addAndroidResult = processManager.runSync(<String>[
flutterBin,
...getLocalEngineArguments(),
'create',
'--template=app',
'--platforms=android',
'.',
], workingDirectory: pluginExampleAppDir.path);
expect(addAndroidResult.exitCode, equals(0),
reason:
'flutter create exited with non 0 code: ${addAndroidResult.stderr}');
// Run flutter build apk to build plugin example project.
final ProcessResult buildApkResult = processManager.runSync(<String>[
flutterBin,
...getLocalEngineArguments(),
'build',
'apk',
'--debug',
], workingDirectory: pluginExampleAppDir.path);
expect(buildApkResult.exitCode, equals(0),
reason:
'flutter build apk exited with non 0 code: ${buildApkResult.stderr}');
});
}
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