Skip to content

Commit 965397c

Browse files
author
Emmanuel Garcia
authored
Fix how Gradle resolves Android plugin (#97823)
1 parent c659ad6 commit 965397c

File tree

3 files changed

+168
-27
lines changed

3 files changed

+168
-27
lines changed

packages/flutter_tools/gradle/app_plugin_loader.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ assert object instanceof Map
2020
assert object.plugins instanceof Map
2121
assert object.plugins.android instanceof List
2222
// Includes the Flutter plugins that support the Android platform.
23+
// This logic must be kept in sync with the logic in flutter.gradle.
2324
object.plugins.android.each { androidPlugin ->
2425
assert androidPlugin.name instanceof String
2526
assert androidPlugin.path instanceof String

packages/flutter_tools/gradle/flutter.gradle

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class FlutterExtension {
4848
* Specifies the relative directory to the Flutter project directory.
4949
* In an app project, this is ../.. since the app's build.gradle is under android/app.
5050
*/
51-
String source
51+
String source = '../..'
5252

5353
/** Allows to override the target file. Otherwise, the target is lib/main.dart. */
5454
String target
@@ -418,7 +418,7 @@ class FlutterPlugin implements Plugin<Project> {
418418

419419
/**
420420
* Compares semantic versions ignoring labels.
421-
*
421+
*
422422
* If the versions are equal (ignoring labels), returns one of the two strings arbitrarily.
423423
*
424424
* If minor or patch are omitted (non-conformant to semantic versioning), they are considered zero.
@@ -459,6 +459,9 @@ class FlutterPlugin implements Plugin<Project> {
459459

460460
getPluginList().each { plugin ->
461461
Project pluginProject = project.rootProject.findProject(plugin.key)
462+
if (pluginProject == null) {
463+
return
464+
}
462465
pluginProject.afterEvaluate {
463466
int pluginCompileSdkVersion = pluginProject.android.compileSdkVersion.substring(8) as int
464467
maxPluginCompileSdkVersion = Math.max(pluginCompileSdkVersion, maxPluginCompileSdkVersion)
@@ -476,15 +479,7 @@ class FlutterPlugin implements Plugin<Project> {
476479
}
477480
}
478481
}
479-
}
480-
}
481-
482-
/**
483-
* Returns `true` if the given path contains an `android/build.gradle` file.
484-
*/
485-
private Boolean doesSupportAndroidPlatform(String path) {
486-
File editableAndroidProject = new File(path, 'android' + File.separator + 'build.gradle')
487-
return editableAndroidProject.exists()
482+
}
488483
}
489484

490485
/**
@@ -495,8 +490,7 @@ class FlutterPlugin implements Plugin<Project> {
495490
private void configurePluginDependencies(Object dependencyObject) {
496491
assert dependencyObject.name instanceof String
497492
Project pluginProject = project.rootProject.findProject(":${dependencyObject.name}")
498-
if (pluginProject == null ||
499-
!doesSupportAndroidPlatform(pluginProject.projectDir.parentFile.path)) {
493+
if (pluginProject == null) {
500494
return
501495
}
502496
assert dependencyObject.dependencies instanceof List
@@ -506,8 +500,7 @@ class FlutterPlugin implements Plugin<Project> {
506500
return
507501
}
508502
Project dependencyProject = project.rootProject.findProject(":$pluginDependencyName")
509-
if (dependencyProject == null ||
510-
!doesSupportAndroidPlatform(dependencyProject.projectDir.parentFile.path)) {
503+
if (dependencyProject == null) {
511504
return
512505
}
513506
// Wait for the Android plugin to load and add the dependency to the plugin project.
@@ -519,17 +512,27 @@ class FlutterPlugin implements Plugin<Project> {
519512
}
520513
}
521514

515+
/** Gets the list of plugins that support the Android platform. */
522516
private Properties getPluginList() {
523-
File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins')
524-
Properties allPlugins = readPropertiesIfExist(pluginsFile)
517+
Map meta = getDependenciesMetadata()
525518
Properties androidPlugins = new Properties()
526-
allPlugins.each { name, path ->
527-
if (doesSupportAndroidPlatform(path)) {
528-
androidPlugins.setProperty(name, path)
519+
if (meta == null) {
520+
return androidPlugins
521+
}
522+
assert meta.plugins instanceof Map
523+
assert meta.plugins.android instanceof List
524+
525+
// This logic must be kept in sync with the logic in app_plugin_loader.gradle.
526+
meta.plugins.android.each { androidPlugin ->
527+
assert androidPlugin.name instanceof String
528+
assert androidPlugin.path instanceof String
529+
// Skip plugins that have no native build (such as a Dart-only implementation
530+
// of a federated plugin).
531+
def needsBuild = androidPlugin.containsKey('native_build') ? androidPlugin['native_build'] : true
532+
if (!needsBuild) {
533+
return
529534
}
530-
// TODO(amirh): log an error if this plugin was specified to be an Android
531-
// plugin according to the new schema, and was missing a build.gradle file.
532-
// https://github.com/flutter/flutter/issues/40784
535+
androidPlugins.setProperty(androidPlugin.name, androidPlugin.path)
533536
}
534537
return androidPlugins
535538
}
@@ -557,14 +560,31 @@ class FlutterPlugin implements Plugin<Project> {
557560
// This means, `plugin-a` depends on `plugin-b` and `plugin-c`.
558561
// `plugin-b` depends on `plugin-c`.
559562
// `plugin-c` doesn't depend on anything.
560-
File pluginsDependencyFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins-dependencies')
563+
Map meta = getDependenciesMetadata()
564+
if (meta == null) {
565+
return []
566+
}
567+
assert meta.dependencyGraph instanceof List
568+
return meta.dependencyGraph
569+
}
570+
571+
private Map parsedFlutterPluginsDependencies
572+
573+
/**
574+
* Parses <project-src>/.flutter-plugins-dependencies
575+
*/
576+
private Map getDependenciesMetadata() {
577+
if (parsedFlutterPluginsDependencies) {
578+
return parsedFlutterPluginsDependencies
579+
}
580+
File pluginsDependencyFile = new File(getFlutterSourceDirectory(), '.flutter-plugins-dependencies')
561581
if (pluginsDependencyFile.exists()) {
562582
def object = new JsonSlurper().parseText(pluginsDependencyFile.text)
563583
assert object instanceof Map
564-
assert object.dependencyGraph instanceof List
565-
return object.dependencyGraph
584+
parsedFlutterPluginsDependencies = object
585+
return object
566586
}
567-
return []
587+
return null
568588
}
569589

570590
private static String toCammelCase(List<String> parts) {
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:file_testing/file_testing.dart';
6+
import 'package:flutter_tools/src/base/file_system.dart';
7+
import 'package:flutter_tools/src/base/io.dart';
8+
import 'package:flutter_tools/src/cache.dart';
9+
10+
import '../src/common.dart';
11+
import 'test_utils.dart';
12+
13+
void main() {
14+
late Directory tempDir;
15+
16+
setUp(() {
17+
Cache.flutterRoot = getFlutterRoot();
18+
tempDir = createResolvedTempDirectorySync('flutter_plugin_test.');
19+
});
20+
21+
tearDown(() async {
22+
tryToDelete(tempDir);
23+
});
24+
25+
// Regression test for https://github.com/flutter/flutter/issues/97729.
26+
test('skip plugin if it does not support the Android platform', () async {
27+
final String flutterBin = fileSystem.path.join(
28+
getFlutterRoot(),
29+
'bin',
30+
'flutter',
31+
);
32+
33+
// Create dummy plugin that *only* supports iOS.
34+
processManager.runSync(<String>[
35+
flutterBin,
36+
...getLocalEngineArguments(),
37+
'create',
38+
'--template=plugin',
39+
'--platforms=ios',
40+
'test_plugin',
41+
], workingDirectory: tempDir.path);
42+
43+
final Directory pluginAppDir = tempDir.childDirectory('test_plugin');
44+
45+
// Create an android directory and a build.gradle file within.
46+
final File pluginGradleFile = pluginAppDir
47+
.childDirectory('android')
48+
.childFile('build.gradle')
49+
..createSync(recursive: true);
50+
expect(pluginGradleFile, exists);
51+
52+
pluginGradleFile.writeAsStringSync(r'''
53+
buildscript {
54+
ext.kotlin_version = '1.5.31'
55+
repositories {
56+
google()
57+
mavenCentral()
58+
}
59+
60+
dependencies {
61+
classpath 'com.android.tools.build:gradle:7.0.0'
62+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
63+
}
64+
65+
configurations.classpath {
66+
resolutionStrategy.activateDependencyLocking()
67+
}
68+
}
69+
70+
allprojects {
71+
repositories {
72+
google()
73+
mavenCentral()
74+
}
75+
}
76+
77+
rootProject.buildDir = '../build'
78+
79+
subprojects {
80+
project.buildDir = "${rootProject.buildDir}/${project.name}"
81+
}
82+
83+
subprojects {
84+
project.evaluationDependsOn(':app')
85+
}
86+
87+
task clean(type: Delete) {
88+
delete rootProject.buildDir
89+
}
90+
''');
91+
92+
final Directory pluginExampleAppDir =
93+
pluginAppDir.childDirectory('example');
94+
95+
// Add android support to the plugin's example app.
96+
final ProcessResult addAndroidResult = processManager.runSync(<String>[
97+
flutterBin,
98+
...getLocalEngineArguments(),
99+
'create',
100+
'--template=app',
101+
'--platforms=android',
102+
'.',
103+
], workingDirectory: pluginExampleAppDir.path);
104+
expect(addAndroidResult.exitCode, equals(0),
105+
reason:
106+
'flutter create exited with non 0 code: ${addAndroidResult.stderr}');
107+
108+
// Run flutter build apk to build plugin example project.
109+
final ProcessResult buildApkResult = processManager.runSync(<String>[
110+
flutterBin,
111+
...getLocalEngineArguments(),
112+
'build',
113+
'apk',
114+
'--debug',
115+
], workingDirectory: pluginExampleAppDir.path);
116+
expect(buildApkResult.exitCode, equals(0),
117+
reason:
118+
'flutter build apk exited with non 0 code: ${buildApkResult.stderr}');
119+
});
120+
}

0 commit comments

Comments
 (0)