[CP][Android] Revert "Reland: "Fix how Gradle resolves Android plugin" (#137115)" (#142491)
Cherry-pick for https://github.com/flutter/flutter/pull/137115 to fix https://github.com/flutter/flutter/issues/141940. Steps taken to create this cherry-pick:
1. Cherry pick commit 995e3fa and fix merge conflicts
2. Delete the following code in `flutter.groovy` that I accidentally kept in the merge but was added in https://github.com/flutter/flutter/pull/137115
```groovy
// Load shared gradle functions
project.apply from: Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools", "gradle", "src", "main", "groovy", "native_plugin_loader.groovy")
```
3. Deleted `android_plugin_skip_unsupported_test.dart` that was left incorrectly by merge.
diff --git a/packages/flutter_tools/gradle/module_plugin_loader.gradle b/packages/flutter_tools/gradle/module_plugin_loader.gradle
index 6e52b5a..2e3a800 100644
--- a/packages/flutter_tools/gradle/module_plugin_loader.gradle
+++ b/packages/flutter_tools/gradle/module_plugin_loader.gradle
@@ -5,26 +5,41 @@
// This file is included from `<module>/.android/include_flutter.groovy`,
// so it can be versioned with the Flutter SDK.
-import java.nio.file.Paths
-
-File pathToThisDirectory = buildscript.sourceFile.parentFile
-apply from: Paths.get(pathToThisDirectory.absolutePath, "src", "main", "groovy", "native_plugin_loader.groovy")
+import groovy.json.JsonSlurper
def moduleProjectRoot = project(':flutter').projectDir.parentFile.parentFile
-List<Map<String, Object>> nativePlugins = nativePluginLoader.getPlugins(moduleProjectRoot)
-nativePlugins.each { androidPlugin ->
- def pluginDirectory = new File(androidPlugin.path as String, 'android')
- assert pluginDirectory.exists()
- include ":${androidPlugin.name}"
- project(":${androidPlugin.name}").projectDir = pluginDirectory
+def object = null;
+String flutterModulePath = project(':flutter').projectDir.parentFile.getAbsolutePath()
+// If this logic is changed, also change the logic in app_plugin_loader.gradle.
+def pluginsFile = new File(moduleProjectRoot, '.flutter-plugins-dependencies')
+if (pluginsFile.exists()) {
+ object = new JsonSlurper().parseText(pluginsFile.text)
+ assert object instanceof Map
+ assert object.plugins instanceof Map
+ assert object.plugins.android instanceof List
+ // Includes the Flutter plugins that support the Android platform.
+ object.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
+ }
+ def pluginDirectory = new File(androidPlugin.path, 'android')
+ assert pluginDirectory.exists()
+ include ":${androidPlugin.name}"
+ project(":${androidPlugin.name}").projectDir = pluginDirectory
+ }
}
-String flutterModulePath = project(':flutter').projectDir.parentFile.getAbsolutePath()
gradle.getGradle().projectsLoaded { g ->
g.rootProject.beforeEvaluate { p ->
p.subprojects { subproject ->
- if (nativePlugins.name.contains(subproject.name)) {
+ if (object != null && object.plugins != null && object.plugins.android != null
+ && object.plugins.android.name.contains(subproject.name)) {
File androidPluginBuildOutputDir = new File(flutterModulePath + File.separator
+ "plugins_build_output" + File.separator + subproject.name);
if (!androidPluginBuildOutputDir.exists()) {
diff --git a/packages/flutter_tools/gradle/src/main/groovy/app_plugin_loader.groovy b/packages/flutter_tools/gradle/src/main/groovy/app_plugin_loader.groovy
index 1696521..402ab64 100644
--- a/packages/flutter_tools/gradle/src/main/groovy/app_plugin_loader.groovy
+++ b/packages/flutter_tools/gradle/src/main/groovy/app_plugin_loader.groovy
@@ -1,29 +1,39 @@
+import groovy.json.JsonSlurper
import org.gradle.api.Plugin
import org.gradle.api.initialization.Settings
-import java.nio.file.Paths
-
apply plugin: FlutterAppPluginLoaderPlugin
class FlutterAppPluginLoaderPlugin implements Plugin<Settings> {
+ // This string must match _kFlutterPluginsHasNativeBuildKey defined in
+ // packages/flutter_tools/lib/src/flutter_plugins.dart.
+ private final String nativeBuildKey = 'native_build'
+
@Override
void apply(Settings settings) {
def flutterProjectRoot = settings.settingsDir.parentFile
- if(!settings.ext.hasProperty('flutterSdkPath')) {
- def properties = new Properties()
- def localPropertiesFile = new File(settings.rootProject.projectDir, "local.properties")
- localPropertiesFile.withInputStream { properties.load(it) }
- settings.ext.flutterSdkPath = properties.getProperty("flutter.sdk")
- assert settings.ext.flutterSdkPath != null, "flutter.sdk not set in local.properties"
+ // If this logic is changed, also change the logic in module_plugin_loader.gradle.
+ def pluginsFile = new File(flutterProjectRoot, '.flutter-plugins-dependencies')
+ if (!pluginsFile.exists()) {
+ return
}
-
- // Load shared gradle functions
- settings.apply from: Paths.get(settings.ext.flutterSdkPath, "packages", "flutter_tools", "gradle", "src", "main", "groovy", "native_plugin_loader.groovy")
- List<Map<String, Object>> nativePlugins = settings.ext.nativePluginLoader.getPlugins(flutterProjectRoot)
- nativePlugins.each { androidPlugin ->
- def pluginDirectory = new File(androidPlugin.path as String, 'android')
+ def object = new JsonSlurper().parseText(pluginsFile.text)
+ assert object instanceof Map
+ assert object.plugins instanceof Map
+ assert object.plugins.android instanceof List
+ // Includes the Flutter plugins that support the Android platform.
+ object.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(nativeBuildKey) ? androidPlugin[nativeBuildKey] : true
+ if (!needsBuild) {
+ return
+ }
+ def pluginDirectory = new File(androidPlugin.path, 'android')
assert pluginDirectory.exists()
settings.include(":${androidPlugin.name}")
settings.project(":${androidPlugin.name}").projectDir = pluginDirectory
diff --git a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
index eb5305c..3616219 100644
--- a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
+++ b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import com.android.build.OutputFile
+import groovy.json.JsonSlurper
import groovy.json.JsonGenerator
import groovy.xml.QName
import java.nio.file.Paths
@@ -64,7 +65,7 @@
* 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.
*/
- String source = "../.."
+ String source
/** Allows to override the target file. Otherwise, the target is lib/main.dart. */
String target
@@ -221,9 +222,6 @@
}
}
- // Load shared gradle functions
- project.apply from: Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools", "gradle", "src", "main", "groovy", "native_plugin_loader.groovy")
-
project.extensions.create("flutter", FlutterExtension)
this.addFlutterTasks(project)
@@ -359,7 +357,7 @@
// 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(project).size() == 0) {
+ if (!isFlutterAppProject() || getPluginList().size() == 0) {
addApiDependencies(project, buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion")
}
@@ -392,77 +390,19 @@
* Configures the Flutter plugin dependencies.
*
* The plugins are added to pubspec.yaml. Then, upon running `flutter pub get`,
- * the tool generates a `.flutter-plugins-dependencies` file, which contains a map to each plugin location.
+ * the tool generates a `.flutter-plugins` file, which contains a 1:1 map to each plugin location.
* Finally, the project's `settings.gradle` loads each plugin's android directory as a subproject.
*/
- private void configurePlugins(Project project) {
- configureLegacyPluginEachProjects(project)
- getPluginList(project).each(this.&configurePluginProject)
- getPluginList(project).each(this.&configurePluginDependencies)
- }
-
- // TODO(54566, 48918): Can remove once the issues are resolved.
- // This means all references to `.flutter-plugins` are then removed and
- // apps only depend exclusively on the `plugins` property in `.flutter-plugins-dependencies`.
- /**
- * Workaround to load non-native plugins for developers who may still use an
- * old `settings.gradle` which includes all the plugins from the
- * `.flutter-plugins` file, even if not made for Android.
- * The settings.gradle then:
- * 1) tries to add the android plugin implementation, which does not
- * exist at all, but is also not included successfully
- * (which does not throw an error and therefore isn't a problem), or
- * 2) includes the plugin successfully as a valid android plugin
- * directory exists, even if the surrounding flutter package does not
- * support the android platform (see e.g. apple_maps_flutter: 1.0.1).
- * So as it's included successfully it expects to be added as API.
- * This is only possible by taking all plugins into account, which
- * only appear on the `dependencyGraph` and in the `.flutter-plugins` file.
- * So in summary the plugins are currently selected from the `dependencyGraph`
- * and filtered then with the [doesSupportAndroidPlatform] method instead of
- * just using the `plugins.android` list.
- */
- private configureLegacyPluginEachProjects(Project project) {
- File settingsGradle = new File(project.projectDir.parentFile, "settings.gradle")
- try {
- if (!settingsGradle.text.contains("'.flutter-plugins'")) {
- return
- }
- } catch (FileNotFoundException ignored) {
- throw new GradleException("settings.gradle does not exist: ${settingsGradle.absolutePath}")
- }
- List<Map<String, Object>> deps = getPluginDependencies(project)
- List<String> plugins = getPluginList(project).collect { it.name as String }
- deps.removeIf { plugins.contains(it.name) }
- deps.each {
- Project pluginProject = project.rootProject.findProject(":${it.name}")
- if (pluginProject == null) {
- // Plugin was not included in `settings.gradle`, but is listed in `.flutter-plugins`.
- project.logger.error("Plugin project :${it.name} listed, but not found. Please fix your settings.gradle.")
- } else if (doesSupportAndroidPlatform(pluginProject.projectDir.parentFile.path as String)) {
- // Plugin has a functioning `android` folder and is included successfully, although it's not supported.
- // It must be configured nonetheless, to not throw an "Unresolved reference" exception.
- configurePluginProject(it)
- } else {
- // Plugin has no or an empty `android` folder. No action required.
- }
- }
- }
-
- // TODO(54566): Can remove this function and its call sites once resolved.
- /**
- * Returns `true` if the given path contains an `android/build.gradle` file.
- */
- private static Boolean doesSupportAndroidPlatform(String path) {
- File editableAndroidProject = new File(path, "android" + File.separator + "build.gradle")
- return editableAndroidProject.exists()
+ private void configurePlugins() {
+ getPluginList().each(this.&configurePluginProject)
+ getPluginDependencies().each(this.&configurePluginDependencies)
}
/** Adds the plugin project dependency to the app project. */
- private void configurePluginProject(Map<String, Object> pluginObject) {
- assert(pluginObject.name instanceof String)
- Project pluginProject = project.rootProject.findProject(":${pluginObject.name}")
+ private void configurePluginProject(String pluginName, String _) {
+ Project pluginProject = project.rootProject.findProject(":$pluginName")
if (pluginProject == null) {
+ project.logger.error("Plugin project :$pluginName not found. Please update settings.gradle.")
return
}
// Add plugin dependency to the app project.
@@ -501,7 +441,7 @@
pluginProject.afterEvaluate {
// Checks if there is a mismatch between the plugin compileSdkVersion and the project compileSdkVersion.
if (pluginProject.android.compileSdkVersion > project.android.compileSdkVersion) {
- project.logger.quiet("Warning: The plugin ${pluginObject.name} requires Android SDK version ${getCompileSdkFromProject(pluginProject)} or higher.")
+ project.logger.quiet("Warning: The plugin ${pluginName} requires Android SDK version ${getCompileSdkFromProject(pluginProject)} or higher.")
project.logger.quiet("For more information about build configuration, see $kWebsiteDeploymentAndroidBuildConfig.")
}
@@ -553,14 +493,10 @@
String ndkVersionIfUnspecified = "21.1.6352462" /* The default for AGP 4.1.0 used in old templates. */
String projectNdkVersion = project.android.ndkVersion ?: ndkVersionIfUnspecified
String maxPluginNdkVersion = projectNdkVersion
- int numProcessedPlugins = getPluginList(project).size()
+ int numProcessedPlugins = getPluginList().size()
- getPluginList(project).each { pluginObject ->
- assert(pluginObject.name instanceof String)
- Project pluginProject = project.rootProject.findProject(":${pluginObject.name}")
- if (pluginProject == null) {
- return
- }
+ getPluginList().each { plugin ->
+ Project pluginProject = project.rootProject.findProject(plugin.key)
pluginProject.afterEvaluate {
// Default to int min if using a preview version to skip the sdk check.
int pluginCompileSdkVersion = Integer.MIN_VALUE;
@@ -595,24 +531,34 @@
}
/**
+ * 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()
+ }
+
+ /**
* Add the dependencies on other plugin projects to the plugin project.
* A plugin A can depend on plugin B. As a result, this dependency must be surfaced by
* making the Gradle plugin project A depend on the Gradle plugin project B.
*/
- private void configurePluginDependencies(Map<String, Object> pluginObject) {
- assert(pluginObject.name instanceof String)
- Project pluginProject = project.rootProject.findProject(":${pluginObject.name}")
- if (pluginProject == null) {
+ private void configurePluginDependencies(Object dependencyObject) {
+ assert(dependencyObject.name instanceof String)
+ Project pluginProject = project.rootProject.findProject(":${dependencyObject.name}")
+ if (pluginProject == null ||
+ !doesSupportAndroidPlatform(pluginProject.projectDir.parentFile.path)) {
return
}
- def dependencies = pluginObject.dependencies
- assert(dependencies instanceof List<String>)
- dependencies.each { pluginDependencyName ->
+ assert(dependencyObject.dependencies instanceof List)
+ dependencyObject.dependencies.each { pluginDependencyName ->
+ assert(pluginDependencyName instanceof String)
if (pluginDependencyName.empty) {
return
}
Project dependencyProject = project.rootProject.findProject(":$pluginDependencyName")
- if (dependencyProject == null) {
+ if (dependencyProject == null ||
+ !doesSupportAndroidPlatform(dependencyProject.projectDir.parentFile.path)) {
return
}
// Wait for the Android plugin to load and add the dependency to the plugin project.
@@ -624,27 +570,52 @@
}
}
- /**
- * Gets the list of plugins (as map) that support the Android platform.
- *
- * The map value contains either the plugins `name` (String),
- * its `path` (String), or its `dependencies` (List<String>).
- * See [NativePluginLoader#getPlugins] in packages/flutter_tools/gradle/src/main/groovy/native_plugin_loader.groovy
- */
- private List<Map<String, Object>> getPluginList(Project project) {
- return project.ext.nativePluginLoader.getPlugins(getFlutterSourceDirectory())
+ private Properties getPluginList() {
+ File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins')
+ Properties allPlugins = readPropertiesIfExist(pluginsFile)
+ Properties androidPlugins = new Properties()
+ allPlugins.each { name, path ->
+ if (doesSupportAndroidPlatform(path)) {
+ androidPlugins.setProperty(name, path)
+ }
+ // TODO(amirh): log an error if this plugin was specified to be an Android
+ // plugin according to the new schema, and was missing a build.gradle file.
+ // https://github.com/flutter/flutter/issues/40784
+ }
+ return androidPlugins
}
- // TODO(54566, 48918): Remove in favor of [getPluginList] only, see also
- // https://github.com/flutter/flutter/blob/1c90ed8b64d9ed8ce2431afad8bc6e6d9acc4556/packages/flutter_tools/lib/src/flutter_plugins.dart#L212
/** Gets the plugins dependencies from `.flutter-plugins-dependencies`. */
- private List<Map<String, Object>> getPluginDependencies(Project project) {
- Map meta = project.ext.nativePluginLoader.getDependenciesMetadata(getFlutterSourceDirectory())
- if (meta == null) {
- return []
+ private List getPluginDependencies() {
+ // Consider a `.flutter-plugins-dependencies` file with the following content:
+ // {
+ // "dependencyGraph": [
+ // {
+ // "name": "plugin-a",
+ // "dependencies": ["plugin-b","plugin-c"]
+ // },
+ // {
+ // "name": "plugin-b",
+ // "dependencies": ["plugin-c"]
+ // },
+ // {
+ // "name": "plugin-c",
+ // "dependencies": []'
+ // }
+ // ]
+ // }
+ //
+ // This means, `plugin-a` depends on `plugin-b` and `plugin-c`.
+ // `plugin-b` depends on `plugin-c`.
+ // `plugin-c` doesn't depend on anything.
+ File pluginsDependencyFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins-dependencies')
+ if (pluginsDependencyFile.exists()) {
+ def object = new JsonSlurper().parseText(pluginsDependencyFile.text)
+ assert(object instanceof Map)
+ assert(object.dependencyGraph instanceof List)
+ return object.dependencyGraph
}
- assert(meta.dependencyGraph instanceof List<Map>)
- return meta.dependencyGraph as List<Map<String, Object>>
+ return []
}
private static String toCamelCase(List<String> parts) {
@@ -1255,7 +1226,7 @@
def nativeAssetsDir = "${project.buildDir}/../native_assets/android/jniLibs/lib/"
project.android.sourceSets.main.jniLibs.srcDir(nativeAssetsDir)
}
- configurePlugins(project)
+ configurePlugins()
detectLowCompileSdkVersionOrNdkVersion()
return
}
@@ -1307,7 +1278,7 @@
}
}
}
- configurePlugins(project)
+ configurePlugins()
detectLowCompileSdkVersionOrNdkVersion()
}
diff --git a/packages/flutter_tools/gradle/src/main/groovy/native_plugin_loader.groovy b/packages/flutter_tools/gradle/src/main/groovy/native_plugin_loader.groovy
deleted file mode 100644
index f9eb7bb..0000000
--- a/packages/flutter_tools/gradle/src/main/groovy/native_plugin_loader.groovy
+++ /dev/null
@@ -1,120 +0,0 @@
-// 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 groovy.json.JsonSlurper
-
-@Singleton
-class NativePluginLoader {
-
- // This string must match _kFlutterPluginsHasNativeBuildKey defined in
- // packages/flutter_tools/lib/src/flutter_plugins.dart.
- static final String nativeBuildKey = "native_build"
- static final String flutterPluginsDependenciesFile = ".flutter-plugins-dependencies"
-
- /**
- * Gets the list of plugins that support the Android platform.
- * The list contains map elements with the following content:
- * {
- * "name": "plugin-a",
- * "path": "/path/to/plugin-a",
- * "dependencies": ["plugin-b", "plugin-c"],
- * "native_build": true
- * }
- *
- * Therefore the map value can either be a `String`, a `List<String>` or a `boolean`.
- */
- List<Map<String, Object>> getPlugins(File flutterSourceDirectory) {
- List<Map<String, Object>> nativePlugins = []
- def meta = getDependenciesMetadata(flutterSourceDirectory)
- if (meta == null) {
- return nativePlugins
- }
-
- assert(meta.plugins instanceof Map<String, Object>)
- def androidPlugins = meta.plugins.android
- assert(androidPlugins instanceof List<Map>)
- // Includes the Flutter plugins that support the Android platform.
- androidPlugins.each { Map<String, Object> androidPlugin ->
- // The property types can be found in _filterPluginsByPlatform defined in
- // packages/flutter_tools/lib/src/flutter_plugins.dart.
- assert(androidPlugin.name instanceof String)
- assert(androidPlugin.path instanceof String)
- assert(androidPlugin.dependencies instanceof List<String>)
- // Skip plugins that have no native build (such as a Dart-only implementation
- // of a federated plugin).
- def needsBuild = androidPlugin.containsKey(nativeBuildKey) ? androidPlugin[nativeBuildKey] : true
- if (needsBuild) {
- nativePlugins.add(androidPlugin)
- }
- }
- return nativePlugins
- }
-
-
- private Map<String, Object> parsedFlutterPluginsDependencies
-
- /**
- * Parses <project-src>/.flutter-plugins-dependencies
- */
- Map<String, Object> getDependenciesMetadata(File flutterSourceDirectory) {
- // Consider a `.flutter-plugins-dependencies` file with the following content:
- // {
- // "plugins": {
- // "android": [
- // {
- // "name": "plugin-a",
- // "path": "/path/to/plugin-a",
- // "dependencies": ["plugin-b", "plugin-c"],
- // "native_build": true
- // },
- // {
- // "name": "plugin-b",
- // "path": "/path/to/plugin-b",
- // "dependencies": ["plugin-c"],
- // "native_build": true
- // },
- // {
- // "name": "plugin-c",
- // "path": "/path/to/plugin-c",
- // "dependencies": [],
- // "native_build": true
- // },
- // ],
- // },
- // "dependencyGraph": [
- // {
- // "name": "plugin-a",
- // "dependencies": ["plugin-b","plugin-c"]
- // },
- // {
- // "name": "plugin-b",
- // "dependencies": ["plugin-c"]
- // },
- // {
- // "name": "plugin-c",
- // "dependencies": []
- // }
- // ]
- // }
- // This means, `plugin-a` depends on `plugin-b` and `plugin-c`.
- // `plugin-b` depends on `plugin-c`.
- // `plugin-c` doesn't depend on anything.
- if (parsedFlutterPluginsDependencies) {
- return parsedFlutterPluginsDependencies
- }
- File pluginsDependencyFile = new File(flutterSourceDirectory, flutterPluginsDependenciesFile)
- if (pluginsDependencyFile.exists()) {
- def object = new JsonSlurper().parseText(pluginsDependencyFile.text)
- assert(object instanceof Map<String, Object>)
- parsedFlutterPluginsDependencies = object
- return object
- }
- return null
- }
-}
-
-// TODO(135392): Remove and use declarative form when migrated
-ext {
- nativePluginLoader = NativePluginLoader.instance
-}
diff --git a/packages/flutter_tools/test/integration.shard/android_plugin_skip_unsupported_test.dart b/packages/flutter_tools/test/integration.shard/android_plugin_skip_unsupported_test.dart
deleted file mode 100644
index 4fdc521..0000000
--- a/packages/flutter_tools/test/integration.shard/android_plugin_skip_unsupported_test.dart
+++ /dev/null
@@ -1,208 +0,0 @@
-// 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_data/plugin_each_settings_gradle_project.dart';
-import 'test_data/plugin_project.dart';
-import 'test_data/project.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 (#137115).
- /// Creates a project which uses a plugin, which is not supported on Android.
- /// This means it has no entry in pubspec.yaml for:
- /// flutter -> plugin -> platforms -> android
- ///
- /// [createAndroidPluginFolder] indicates that the plugin can additionally
- /// have a functioning `android` folder.
- Future<ProcessResult> testUnsupportedPlugin({
- required Project project,
- required bool createAndroidPluginFolder,
- }) async {
- final String flutterBin = fileSystem.path.join(
- getFlutterRoot(),
- 'bin',
- 'flutter',
- );
-
- // Create dummy plugin that supports iOS and optionally Android.
- processManager.runSync(<String>[
- flutterBin,
- ...getLocalEngineArguments(),
- 'create',
- '--template=plugin',
- '--platforms=ios${createAndroidPluginFolder ? ',android' : ''}',
- 'test_plugin',
- ], workingDirectory: tempDir.path);
-
- final Directory pluginAppDir = tempDir.childDirectory('test_plugin');
-
- final File pubspecFile = pluginAppDir.childFile('pubspec.yaml');
- String pubspecYamlSrc =
- pubspecFile.readAsStringSync().replaceAll('\r\n', '\n');
- if (createAndroidPluginFolder) {
- // Override pubspec to drop support for the Android implementation.
- pubspecYamlSrc = pubspecYamlSrc
- .replaceFirst(
- RegExp(r'name:.*\n'),
- 'name: test_plugin\n',
- )
- .replaceFirst('''
- android:
- package: com.example.test_plugin
- pluginClass: TestPlugin
-''', '''
-# android:
-# package: com.example.test_plugin
-# pluginClass: TestPlugin
-''');
-
- pubspecFile.writeAsStringSync(pubspecYamlSrc);
-
- // Check the android directory and the build.gradle file within.
- final File pluginGradleFile =
- pluginAppDir.childDirectory('android').childFile('build.gradle');
- expect(pluginGradleFile, exists);
- } else {
- expect(pubspecYamlSrc, isNot(contains('android:')));
- }
-
- // Create a project which includes the plugin to test against
- final Directory pluginExampleAppDir =
- pluginAppDir.childDirectory('example');
-
- await project.setUpIn(pluginExampleAppDir);
-
- // Run flutter build apk to build plugin example project.
- return processManager.runSync(<String>[
- flutterBin,
- ...getLocalEngineArguments(),
- 'build',
- 'apk',
- '--debug',
- ], workingDirectory: pluginExampleAppDir.path);
- }
-
- test('skip plugin if it does not support the Android platform', () async {
- final Project project = PluginWithPathAndroidProject();
- final ProcessResult buildApkResult = await testUnsupportedPlugin(
- project: project, createAndroidPluginFolder: false);
- expect(buildApkResult.stderr.toString(),
- isNot(contains('Please fix your settings.gradle.')));
- expect(buildApkResult, const ProcessResultMatcher());
- });
-
- test(
- 'skip plugin with android folder if it does not support the Android platform',
- () async {
- final Project project = PluginWithPathAndroidProject();
- final ProcessResult buildApkResult = await testUnsupportedPlugin(
- project: project, createAndroidPluginFolder: true);
- expect(buildApkResult.stderr.toString(),
- isNot(contains('Please fix your settings.gradle.')));
- expect(buildApkResult, const ProcessResultMatcher());
- });
-
- // TODO(54566): Remove test when issue is resolved.
- /// Test project with a `settings.gradle` (PluginEach) that apps were created
- /// with until Flutter v1.22.0.
- /// It uses the `.flutter-plugins` file to load EACH plugin.
- test(
- 'skip plugin if it does not support the Android platform with a _plugin.each_ settings.gradle',
- () async {
- final Project project = PluginEachWithPathAndroidProject();
- final ProcessResult buildApkResult = await testUnsupportedPlugin(
- project: project, createAndroidPluginFolder: false);
- expect(buildApkResult.stderr.toString(),
- isNot(contains('Please fix your settings.gradle.')));
- expect(buildApkResult, const ProcessResultMatcher());
- });
-
- // TODO(54566): Remove test when issue is resolved.
- /// Test project with a `settings.gradle` (PluginEach) that apps were created
- /// with until Flutter v1.22.0.
- /// It uses the `.flutter-plugins` file to load EACH plugin.
- /// The plugin includes a functional 'android' folder.
- test(
- 'skip plugin with android folder if it does not support the Android platform with a _plugin.each_ settings.gradle',
- () async {
- final Project project = PluginEachWithPathAndroidProject();
- final ProcessResult buildApkResult = await testUnsupportedPlugin(
- project: project, createAndroidPluginFolder: true);
- expect(buildApkResult.stderr.toString(),
- isNot(contains('Please fix your settings.gradle.')));
- expect(buildApkResult, const ProcessResultMatcher());
- });
-
- // TODO(54566): Remove test when issue is resolved.
- /// Test project with a `settings.gradle` (PluginEach) that apps were created
- /// with until Flutter v1.22.0.
- /// It is compromised by removing the 'include' statement of the plugins.
- /// As the "'.flutter-plugins'" keyword is still present, the framework
- /// assumes that all plugins are included, which is not the case.
- /// Therefore it should throw an error.
- test(
- 'skip plugin if it does not support the Android platform with a compromised _plugin.each_ settings.gradle',
- () async {
- final Project project = PluginCompromisedEachWithPathAndroidProject();
- final ProcessResult buildApkResult = await testUnsupportedPlugin(
- project: project, createAndroidPluginFolder: true);
- expect(
- buildApkResult,
- const ProcessResultMatcher(
- stderrPattern: 'Please fix your settings.gradle.'),
- );
- });
-}
-
-const String pubspecWithPluginPath = r'''
-name: test
-environment:
- sdk: '>=3.2.0-0 <4.0.0'
-dependencies:
- flutter:
- sdk: flutter
-
- test_plugin:
- path: ../
-''';
-
-/// Project that load's a plugin from the specified path.
-class PluginWithPathAndroidProject extends PluginProject {
- @override
- String get pubspec => pubspecWithPluginPath;
-}
-
-// TODO(54566): Remove class when issue is resolved.
-/// [PluginEachSettingsGradleProject] that load's a plugin from the specified
-/// path.
-class PluginEachWithPathAndroidProject extends PluginEachSettingsGradleProject {
- @override
- String get pubspec => pubspecWithPluginPath;
-}
-
-// TODO(54566): Remove class when issue is resolved.
-/// [PluginCompromisedEachSettingsGradleProject] that load's a plugin from the
-/// specified path.
-class PluginCompromisedEachWithPathAndroidProject
- extends PluginCompromisedEachSettingsGradleProject {
- @override
- String get pubspec => pubspecWithPluginPath;
-}
diff --git a/packages/flutter_tools/test/integration.shard/test_data/plugin_each_settings_gradle_project.dart b/packages/flutter_tools/test/integration.shard/test_data/plugin_each_settings_gradle_project.dart
deleted file mode 100644
index 7747bed..0000000
--- a/packages/flutter_tools/test/integration.shard/test_data/plugin_each_settings_gradle_project.dart
+++ /dev/null
@@ -1,60 +0,0 @@
-// 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.
-
-// TODO(54566): Remove this file when issue is resolved.
-
-import 'deferred_components_config.dart';
-import 'plugin_project.dart';
-
-/// Project to test the deprecated `settings.gradle` (PluginEach) that apps were
-/// created with until Flutter v1.22.0.
-/// It uses the `.flutter-plugins` file to load EACH plugin.
-class PluginEachSettingsGradleProject extends PluginProject {
- @override
- DeferredComponentsConfig get deferredComponents =>
- PluginEachSettingsGradleDeferredComponentsConfig();
-}
-
-class PluginEachSettingsGradleDeferredComponentsConfig
- extends PluginDeferredComponentsConfig {
- @override
- String get androidSettings => r'''
-include ':app'
-def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
-def plugins = new Properties()
-def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
-if (pluginsFile.exists()) {
- pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
-}
-plugins.each { name, path ->
- def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
- include ":$name"
- project(":$name").projectDir = pluginDirectory
-}
- ''';
-}
-
-/// Project to test the deprecated `settings.gradle` (PluginEach) that apps were
-/// created with until Flutter v1.22.0.
-/// It uses the `.flutter-plugins` file to get EACH plugin.
-/// It is compromised by removing the 'include' statement of the plugins.
-class PluginCompromisedEachSettingsGradleProject extends PluginProject {
- @override
- DeferredComponentsConfig get deferredComponents =>
- PluginCompromisedEachSettingsGradleDeferredComponentsConfig();
-}
-
-class PluginCompromisedEachSettingsGradleDeferredComponentsConfig
- extends PluginDeferredComponentsConfig {
- @override
- String get androidSettings => r'''
-include ':app'
-def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
-def plugins = new Properties()
-def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
-if (pluginsFile.exists()) {
- pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
-}
- ''';
-}
diff --git a/packages/flutter_tools/test/integration.shard/test_data/plugin_project.dart b/packages/flutter_tools/test/integration.shard/test_data/plugin_project.dart
deleted file mode 100644
index 2714ca6..0000000
--- a/packages/flutter_tools/test/integration.shard/test_data/plugin_project.dart
+++ /dev/null
@@ -1,99 +0,0 @@
-// 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 'basic_project.dart';
-import 'deferred_components_config.dart';
-import 'deferred_components_project.dart';
-
-/// Project which can load native plugins
-class PluginProject extends BasicProject {
- @override
- final DeferredComponentsConfig? deferredComponents =
- PluginDeferredComponentsConfig();
-}
-
-class PluginDeferredComponentsConfig extends BasicDeferredComponentsConfig {
- @override
- String get androidBuild => r'''
-buildscript {
- ext.kotlin_version = '1.7.10'
- repositories {
- google()
- mavenCentral()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:7.3.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')
- dependencyLocking {
- ignoredDependencies.add('io.flutter:*')
- lockFile = file("${rootProject.projectDir}/project-${project.name}.lockfile")
- if (!project.hasProperty('local-engine-repo')) {
- lockAllConfigurations()
- }
- }
-}
-tasks.register("clean", Delete) {
- delete rootProject.buildDir
-}
-''';
-
- @override
- String get androidSettings => r'''
-include ':app'
-
-def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
-def properties = new Properties()
-
-assert localPropertiesFile.exists()
-localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
-
-def flutterSdkPath = properties.getProperty("flutter.sdk")
-assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
-apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
-''';
-
- @override
- String get appManifest => r'''
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.yourcompany.flavors">
- <uses-permission android:name="android.permission.INTERNET"/>
- <application
- android:name="${applicationName}"
- android:label="flavors">
- <activity
- android:name=".MainActivity"
- android:exported="true"
- android:launchMode="singleTop"
- android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
- android:hardwareAccelerated="true"
- android:windowSoftInputMode="adjustResize">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- <meta-data
- android:name="flutterEmbedding"
- android:value="2" />
- </application>
-</manifest>
-''';
-}