Unverified Commit fcf341e4 authored by Francisco Magdaleno's avatar Francisco Magdaleno Committed by GitHub

Reland "[flutter_tools] Removes the need of a no-op plugin implementations #48614" (#49085)

parent 2b998405
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/framework.dart';
...@@ -74,10 +75,6 @@ Future<void> main() async { ...@@ -74,10 +75,6 @@ Future<void> main() async {
); );
}); });
// https://github.com/flutter/flutter/issues/46898
// https://github.com/flutter/flutter/issues/39657
File(path.join(pluginCDirectory.path, 'android', 'build.gradle')).deleteSync();
final File pluginCpubspec = File(path.join(pluginCDirectory.path, 'pubspec.yaml')); final File pluginCpubspec = File(path.join(pluginCDirectory.path, 'pubspec.yaml'));
await pluginCpubspec.writeAsString(''' await pluginCpubspec.writeAsString('''
name: plugin_c name: plugin_c
...@@ -177,30 +174,32 @@ public class DummyPluginAClass { ...@@ -177,30 +174,32 @@ public class DummyPluginAClass {
} }
final String flutterPluginsDependenciesFileContent = flutterPluginsDependenciesFile.readAsStringSync(); final String flutterPluginsDependenciesFileContent = flutterPluginsDependenciesFile.readAsStringSync();
final Map<String, dynamic> jsonContent = json.decode(flutterPluginsDependenciesFileContent) as Map<String, dynamic>;
// Verify the dependencyGraph object is valid. The rest of the contents of this file are not relevant to the
// dependency graph and are tested by unit tests.
final List<dynamic> dependencyGraph = jsonContent['dependencyGraph'] as List<dynamic>;
const String kExpectedPluginsDependenciesContent = const String kExpectedPluginsDependenciesContent =
'{' '['
'\"_info\":\"// This is a generated file; do not edit or check into version control.\",' '{'
'\"dependencyGraph\":[' '\"name\":\"plugin_a\",'
'{' '\"dependencies\":[\"plugin_b\",\"plugin_c\"]'
'\"name\":\"plugin_a\",' '},'
'\"dependencies\":[\"plugin_b\",\"plugin_c\"]' '{'
'},' '\"name\":\"plugin_b\",'
'{' '\"dependencies\":[]'
'\"name\":\"plugin_b\",' '},'
'\"dependencies\":[]' '{'
'},' '\"name\":\"plugin_c\",'
'{' '\"dependencies\":[]'
'\"name\":\"plugin_c\",' '}'
'\"dependencies\":[]' ']';
'}' final String graphString = json.encode(dependencyGraph);
']' if (graphString != kExpectedPluginsDependenciesContent) {
'}';
if (flutterPluginsDependenciesFileContent != kExpectedPluginsDependenciesContent) {
return TaskResult.failure( return TaskResult.failure(
'Unexpected file content in ${flutterPluginsDependenciesFile.path}: ' 'Unexpected file content in ${flutterPluginsDependenciesFile.path}: '
'Found "$flutterPluginsDependenciesFileContent" instead of ' 'Found "$graphString" instead of "$kExpectedPluginsDependenciesContent"'
'"$kExpectedPluginsDependenciesContent"'
); );
} }
......
...@@ -11,6 +11,7 @@ import 'package:yaml/yaml.dart'; ...@@ -11,6 +11,7 @@ import 'package:yaml/yaml.dart';
import 'android/gradle.dart'; import 'android/gradle.dart';
import 'base/common.dart'; import 'base/common.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'base/time.dart';
import 'convert.dart'; import 'convert.dart';
import 'dart/package_map.dart'; import 'dart/package_map.dart';
import 'features.dart'; import 'features.dart';
...@@ -18,6 +19,7 @@ import 'globals.dart' as globals; ...@@ -18,6 +19,7 @@ import 'globals.dart' as globals;
import 'macos/cocoapods.dart'; import 'macos/cocoapods.dart';
import 'platform_plugins.dart'; import 'platform_plugins.dart';
import 'project.dart'; import 'project.dart';
import 'version.dart';
void _renderTemplateToFile(String template, dynamic context, String filePath) { void _renderTemplateToFile(String template, dynamic context, String filePath) {
final String renderedTemplate = final String renderedTemplate =
...@@ -263,7 +265,7 @@ class Plugin { ...@@ -263,7 +265,7 @@ class Plugin {
final Map<String, PluginPlatform> platforms; final Map<String, PluginPlatform> platforms;
} }
Plugin _pluginFromPubspec(String name, Uri packageRoot) { Plugin _pluginFromPackage(String name, Uri packageRoot) {
final String pubspecPath = globals.fs.path.fromUri(packageRoot.resolve('pubspec.yaml')); final String pubspecPath = globals.fs.path.fromUri(packageRoot.resolve('pubspec.yaml'));
if (!globals.fs.isFileSync(pubspecPath)) { if (!globals.fs.isFileSync(pubspecPath)) {
return null; return null;
...@@ -302,7 +304,7 @@ List<Plugin> findPlugins(FlutterProject project) { ...@@ -302,7 +304,7 @@ List<Plugin> findPlugins(FlutterProject project) {
} }
packages.forEach((String name, Uri uri) { packages.forEach((String name, Uri uri) {
final Uri packageRoot = uri.resolve('..'); final Uri packageRoot = uri.resolve('..');
final Plugin plugin = _pluginFromPubspec(name, packageRoot); final Plugin plugin = _pluginFromPackage(name, packageRoot);
if (plugin != null) { if (plugin != null) {
plugins.add(plugin); plugins.add(plugin);
} }
...@@ -310,55 +312,163 @@ List<Plugin> findPlugins(FlutterProject project) { ...@@ -310,55 +312,163 @@ List<Plugin> findPlugins(FlutterProject project) {
return plugins; return plugins;
} }
/// Writes the .flutter-plugins and .flutter-plugins-dependencies files based on the list of plugins. /// Filters [plugins] to those supported by [platformKey].
/// If there aren't any plugins, then the files aren't written to disk. List<Map<String, dynamic>> _filterPluginsByPlatform(List<Plugin>plugins, String platformKey) {
final Iterable<Plugin> platformPlugins = plugins.where((Plugin p) {
return p.platforms.containsKey(platformKey);
});
final Set<String> pluginNames = platformPlugins.map((Plugin plugin) => plugin.name).toSet();
final List<Map<String, dynamic>> list = <Map<String, dynamic>>[];
for (final Plugin plugin in platformPlugins) {
list.add(<String, dynamic>{
'name': plugin.name,
'path': fsUtils.escapePath(plugin.path),
'dependencies': <String>[...plugin.dependencies.where(pluginNames.contains)],
});
}
return list;
}
/// Writes the .flutter-plugins-dependencies file based on the list of plugins.
/// If there aren't any plugins, then the files aren't written to disk. The resulting
/// file looks something like this (order of keys is not guaranteed):
/// {
/// "info": "This is a generated file; do not edit or check into version control.",
/// "plugins": {
/// "ios": [
/// {
/// "name": "test",
/// "path": "test_path",
/// "dependencies": [
/// "plugin-a",
/// "plugin-b"
/// ]
/// }
/// ],
/// "android": [],
/// "macos": [],
/// "linux": [],
/// "windows": [],
/// "web": []
/// },
/// "dependencyGraph": [
/// {
/// "name": "plugin-a",
/// "dependencies": [
/// "plugin-b",
/// "plugin-c"
/// ]
/// },
/// {
/// "name": "plugin-b",
/// "dependencies": [
/// "plugin-c"
/// ]
/// },
/// {
/// "name": "plugin-c",
/// "dependencies": []
/// }
/// ],
/// "date_created": "1970-01-01 00:00:00.000",
/// "version": "0.0.0-unknown"
/// }
/// ///
/// Finally, returns [true] if .flutter-plugins or .flutter-plugins-dependencies have changed, ///
/// otherwise returns [false]. /// Finally, returns [true] if the plugins list has changed, otherwise returns [false].
bool _writeFlutterPluginsList(FlutterProject project, List<Plugin> plugins) { bool _writeFlutterPluginsList(FlutterProject project, List<Plugin> plugins) {
final List<dynamic> directAppDependencies = <dynamic>[]; final File pluginsFile = project.flutterPluginsDependenciesFile;
const String info = 'This is a generated file; do not edit or check into version control.'; if (plugins.isEmpty) {
final StringBuffer flutterPluginsBuffer = StringBuffer('# $info\n'); if (pluginsFile.existsSync()) {
pluginsFile.deleteSync();
return true;
}
return false;
}
final Set<String> pluginNames = <String>{}; final String iosKey = project.ios.pluginConfigKey;
for (final Plugin plugin in plugins) { final String androidKey = project.android.pluginConfigKey;
pluginNames.add(plugin.name); final String macosKey = project.macos.pluginConfigKey;
final String linuxKey = project.linux.pluginConfigKey;
final String windowsKey = project.windows.pluginConfigKey;
final String webKey = project.web.pluginConfigKey;
final Map<String, dynamic> pluginsMap = <String, dynamic>{};
pluginsMap[iosKey] = _filterPluginsByPlatform(plugins, iosKey);
pluginsMap[androidKey] = _filterPluginsByPlatform(plugins, androidKey);
pluginsMap[macosKey] = _filterPluginsByPlatform(plugins, macosKey);
pluginsMap[linuxKey] = _filterPluginsByPlatform(plugins, linuxKey);
pluginsMap[windowsKey] = _filterPluginsByPlatform(plugins, windowsKey);
pluginsMap[webKey] = _filterPluginsByPlatform(plugins, webKey);
final Map<String, dynamic> result = <String, dynamic> {};
result['info'] = 'This is a generated file; do not edit or check into version control.';
result['plugins'] = pluginsMap;
/// The dependencyGraph object is kept for backwards compatibility, but
/// should be removed once migration is complete.
/// https://github.com/flutter/flutter/issues/48918
result['dependencyGraph'] = _createPluginLegacyDependencyGraph(plugins);
result['date_created'] = systemClock.now().toString();
result['version'] = flutterVersion.frameworkVersion;
// Only notify if the plugins list has changed. [date_created] will always be different,
// [version] is not relevant for this check.
final String oldPluginsFileStringContent = _readFileContent(pluginsFile);
bool pluginsChanged = true;
if (oldPluginsFileStringContent != null) {
pluginsChanged = oldPluginsFileStringContent.contains(pluginsMap.toString());
} }
final String pluginFileContent = json.encode(result);
pluginsFile.writeAsStringSync(pluginFileContent, flush: true);
return pluginsChanged;
}
List<dynamic> _createPluginLegacyDependencyGraph(List<Plugin> plugins) {
final List<dynamic> directAppDependencies = <dynamic>[];
final Set<String> pluginNames = plugins.map((Plugin plugin) => plugin.name).toSet();
for (final Plugin plugin in plugins) { for (final Plugin plugin in plugins) {
flutterPluginsBuffer.write('${plugin.name}=${fsUtils.escapePath(plugin.path)}\n');
directAppDependencies.add(<String, dynamic>{ directAppDependencies.add(<String, dynamic>{
'name': plugin.name, 'name': plugin.name,
// Extract the plugin dependencies which happen to be plugins. // Extract the plugin dependencies which happen to be plugins.
'dependencies': <String>[...plugin.dependencies.where(pluginNames.contains)], 'dependencies': <String>[...plugin.dependencies.where(pluginNames.contains)],
}); });
} }
return directAppDependencies;
}
// The .flutter-plugins file will be DEPRECATED in favor of .flutter-plugins-dependencies.
// TODO(franciscojma): Remove this method once deprecated.
// https://github.com/flutter/flutter/issues/48918
//
/// Writes the .flutter-plugins files based on the list of plugins.
/// If there aren't any plugins, then the files aren't written to disk.
///
/// Finally, returns [true] if .flutter-plugins has changed, otherwise returns [false].
bool _writeFlutterPluginsListLegacy(FlutterProject project, List<Plugin> plugins) {
final File pluginsFile = project.flutterPluginsFile; final File pluginsFile = project.flutterPluginsFile;
final String oldPluginFileContent = _readFileContent(pluginsFile); if (plugins.isEmpty) {
final String pluginFileContent = flutterPluginsBuffer.toString();
if (pluginNames.isNotEmpty) {
pluginsFile.writeAsStringSync(pluginFileContent, flush: true);
} else {
if (pluginsFile.existsSync()) { if (pluginsFile.existsSync()) {
pluginsFile.deleteSync(); pluginsFile.deleteSync();
return true;
} }
return false;
} }
final File dependenciesFile = project.flutterPluginsDependenciesFile; const String info = 'This is a generated file; do not edit or check into version control.';
final String oldDependenciesFileContent = _readFileContent(dependenciesFile); final StringBuffer flutterPluginsBuffer = StringBuffer('# $info\n');
final String dependenciesFileContent = json.encode(<String, dynamic>{
'_info': '// $info', for (final Plugin plugin in plugins) {
'dependencyGraph': directAppDependencies, flutterPluginsBuffer.write('${plugin.name}=${fsUtils.escapePath(plugin.path)}\n');
});
if (pluginNames.isNotEmpty) {
dependenciesFile.writeAsStringSync(dependenciesFileContent, flush: true);
} else {
if (dependenciesFile.existsSync()) {
dependenciesFile.deleteSync();
}
} }
final String oldPluginFileContent = _readFileContent(pluginsFile);
final String pluginFileContent = flutterPluginsBuffer.toString();
pluginsFile.writeAsStringSync(pluginFileContent, flush: true);
return oldPluginFileContent != _readFileContent(pluginsFile) return oldPluginFileContent != _readFileContent(pluginsFile);
|| oldDependenciesFileContent != _readFileContent(dependenciesFile);
} }
/// Returns the contents of [File] or [null] if that file does not exist. /// Returns the contents of [File] or [null] if that file does not exist.
...@@ -782,8 +892,13 @@ Future<void> _writeWebPluginRegistrant(FlutterProject project, List<Plugin> plug ...@@ -782,8 +892,13 @@ Future<void> _writeWebPluginRegistrant(FlutterProject project, List<Plugin> plug
/// Assumes `pub get` has been executed since last change to `pubspec.yaml`. /// Assumes `pub get` has been executed since last change to `pubspec.yaml`.
void refreshPluginsList(FlutterProject project, {bool checkProjects = false}) { void refreshPluginsList(FlutterProject project, {bool checkProjects = false}) {
final List<Plugin> plugins = findPlugins(project); final List<Plugin> plugins = findPlugins(project);
// TODO(franciscojma): Remove once migration is complete.
// Write the legacy plugin files to avoid breaking existing apps.
final bool legacyChanged = _writeFlutterPluginsListLegacy(project, plugins);
final bool changed = _writeFlutterPluginsList(project, plugins); final bool changed = _writeFlutterPluginsList(project, plugins);
if (changed) { if (changed || legacyChanged) {
if (!checkProjects || project.ios.existsSync()) { if (!checkProjects || project.ios.existsSync()) {
cocoaPods.invalidatePodInstallOutput(project.ios); cocoaPods.invalidatePodInstallOutput(project.ios);
} }
......
...@@ -20,6 +20,7 @@ import 'flutter_manifest.dart'; ...@@ -20,6 +20,7 @@ import 'flutter_manifest.dart';
import 'globals.dart' as globals; import 'globals.dart' as globals;
import 'ios/plist_parser.dart'; import 'ios/plist_parser.dart';
import 'ios/xcodeproj.dart' as xcode; import 'ios/xcodeproj.dart' as xcode;
import 'platform_plugins.dart';
import 'plugins.dart'; import 'plugins.dart';
import 'template.dart'; import 'template.dart';
...@@ -251,6 +252,16 @@ class FlutterProject { ...@@ -251,6 +252,16 @@ class FlutterProject {
} }
} }
/// Base class for projects per platform.
abstract class FlutterProjectPlatform {
/// Plugin's platform config key, e.g., "macos", "ios".
String get pluginConfigKey;
/// Whether the platform exists in the project.
bool existsSync();
}
/// Represents an Xcode-based sub-project. /// Represents an Xcode-based sub-project.
/// ///
/// This defines interfaces common to iOS and macOS projects. /// This defines interfaces common to iOS and macOS projects.
...@@ -300,12 +311,15 @@ abstract class XcodeBasedProject { ...@@ -300,12 +311,15 @@ abstract class XcodeBasedProject {
/// ///
/// Instances will reflect the contents of the `ios/` sub-folder of /// Instances will reflect the contents of the `ios/` sub-folder of
/// Flutter applications and the `.ios/` sub-folder of Flutter module projects. /// Flutter applications and the `.ios/` sub-folder of Flutter module projects.
class IosProject implements XcodeBasedProject { class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
IosProject.fromFlutter(this.parent); IosProject.fromFlutter(this.parent);
@override @override
final FlutterProject parent; final FlutterProject parent;
@override
String get pluginConfigKey => IOSPlugin.kConfigKey;
static final RegExp _productBundleIdPattern = RegExp(r'''^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(["']?)(.*?)\1;\s*$'''); static final RegExp _productBundleIdPattern = RegExp(r'''^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(["']?)(.*?)\1;\s*$''');
static const String _productBundleIdVariable = r'$(PRODUCT_BUNDLE_IDENTIFIER)'; static const String _productBundleIdVariable = r'$(PRODUCT_BUNDLE_IDENTIFIER)';
static const String _hostAppBundleName = 'Runner'; static const String _hostAppBundleName = 'Runner';
...@@ -574,12 +588,15 @@ class IosProject implements XcodeBasedProject { ...@@ -574,12 +588,15 @@ class IosProject implements XcodeBasedProject {
/// ///
/// Instances will reflect the contents of the `android/` sub-folder of /// Instances will reflect the contents of the `android/` sub-folder of
/// Flutter applications and the `.android/` sub-folder of Flutter module projects. /// Flutter applications and the `.android/` sub-folder of Flutter module projects.
class AndroidProject { class AndroidProject extends FlutterProjectPlatform {
AndroidProject._(this.parent); AndroidProject._(this.parent);
/// The parent of this project. /// The parent of this project.
final FlutterProject parent; final FlutterProject parent;
@override
String get pluginConfigKey => AndroidPlugin.kConfigKey;
static final RegExp _applicationIdPattern = RegExp('^\\s*applicationId\\s+[\'\"](.*)[\'\"]\\s*\$'); static final RegExp _applicationIdPattern = RegExp('^\\s*applicationId\\s+[\'\"](.*)[\'\"]\\s*\$');
static final RegExp _kotlinPluginPattern = RegExp('^\\s*apply plugin\:\\s+[\'\"]kotlin-android[\'\"]\\s*\$'); static final RegExp _kotlinPluginPattern = RegExp('^\\s*apply plugin\:\\s+[\'\"]kotlin-android[\'\"]\\s*\$');
static final RegExp _groupPattern = RegExp('^\\s*group\\s+[\'\"](.*)[\'\"]\\s*\$'); static final RegExp _groupPattern = RegExp('^\\s*group\\s+[\'\"](.*)[\'\"]\\s*\$');
...@@ -627,6 +644,7 @@ class AndroidProject { ...@@ -627,6 +644,7 @@ class AndroidProject {
} }
/// Whether the current flutter project has an Android sub-project. /// Whether the current flutter project has an Android sub-project.
@override
bool existsSync() { bool existsSync() {
return parent.isModule || _editableHostAppDirectory.existsSync(); return parent.isModule || _editableHostAppDirectory.existsSync();
} }
...@@ -760,12 +778,16 @@ enum AndroidEmbeddingVersion { ...@@ -760,12 +778,16 @@ enum AndroidEmbeddingVersion {
} }
/// Represents the web sub-project of a Flutter project. /// Represents the web sub-project of a Flutter project.
class WebProject { class WebProject extends FlutterProjectPlatform {
WebProject._(this.parent); WebProject._(this.parent);
final FlutterProject parent; final FlutterProject parent;
@override
String get pluginConfigKey => WebPlugin.kConfigKey;
/// Whether this flutter project has a web sub-project. /// Whether this flutter project has a web sub-project.
@override
bool existsSync() { bool existsSync() {
return parent.directory.childDirectory('web').existsSync() return parent.directory.childDirectory('web').existsSync()
&& indexFile.existsSync(); && indexFile.existsSync();
...@@ -810,12 +832,15 @@ Match _firstMatchInFile(File file, RegExp regExp) { ...@@ -810,12 +832,15 @@ Match _firstMatchInFile(File file, RegExp regExp) {
} }
/// The macOS sub project. /// The macOS sub project.
class MacOSProject implements XcodeBasedProject { class MacOSProject extends FlutterProjectPlatform implements XcodeBasedProject {
MacOSProject._(this.parent); MacOSProject._(this.parent);
@override @override
final FlutterProject parent; final FlutterProject parent;
@override
String get pluginConfigKey => MacOSPlugin.kConfigKey;
static const String _hostAppBundleName = 'Runner'; static const String _hostAppBundleName = 'Runner';
@override @override
...@@ -895,11 +920,15 @@ class MacOSProject implements XcodeBasedProject { ...@@ -895,11 +920,15 @@ class MacOSProject implements XcodeBasedProject {
} }
/// The Windows sub project /// The Windows sub project
class WindowsProject { class WindowsProject extends FlutterProjectPlatform {
WindowsProject._(this.project); WindowsProject._(this.project);
final FlutterProject project; final FlutterProject project;
@override
String get pluginConfigKey => WindowsPlugin.kConfigKey;
@override
bool existsSync() => _editableDirectory.existsSync(); bool existsSync() => _editableDirectory.existsSync();
Directory get _editableDirectory => project.directory.childDirectory('windows'); Directory get _editableDirectory => project.directory.childDirectory('windows');
...@@ -933,11 +962,14 @@ class WindowsProject { ...@@ -933,11 +962,14 @@ class WindowsProject {
} }
/// The Linux sub project. /// The Linux sub project.
class LinuxProject { class LinuxProject extends FlutterProjectPlatform {
LinuxProject._(this.project); LinuxProject._(this.project);
final FlutterProject project; final FlutterProject project;
@override
String get pluginConfigKey => LinuxPlugin.kConfigKey;
Directory get _editableDirectory => project.directory.childDirectory('linux'); Directory get _editableDirectory => project.directory.childDirectory('linux');
/// The directory in the project that is managed by Flutter. As much as /// The directory in the project that is managed by Flutter. As much as
...@@ -950,6 +982,7 @@ class LinuxProject { ...@@ -950,6 +982,7 @@ class LinuxProject {
/// checked in should live here. /// checked in should live here.
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral'); Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
@override
bool existsSync() => _editableDirectory.existsSync(); bool existsSync() => _editableDirectory.existsSync();
/// The Linux project makefile. /// The Linux project makefile.
......
...@@ -16,6 +16,8 @@ import 'cache.dart'; ...@@ -16,6 +16,8 @@ import 'cache.dart';
import 'convert.dart'; import 'convert.dart';
import 'globals.dart' as globals; import 'globals.dart' as globals;
FlutterVersion get flutterVersion => context.get<FlutterVersion>();
class FlutterVersion { class FlutterVersion {
FlutterVersion([this._clock = const SystemClock()]) { FlutterVersion([this._clock = const SystemClock()]) {
_frameworkRevision = _runGit(gitLog(<String>['-n', '1', '--pretty=format:%H']).join(' ')); _frameworkRevision = _runGit(gitLog(<String>['-n', '1', '--pretty=format:%H']).join(' '));
......
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