Unverified Commit 5eb394e0 authored by Francisco Magdaleno's avatar Francisco Magdaleno Committed by GitHub

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

parent 91f8d3da
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter_devicelab/framework/framework.dart';
......@@ -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'));
await pluginCpubspec.writeAsString('''
name: plugin_c
......@@ -177,10 +174,14 @@ public class DummyPluginAClass {
}
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 =
'{'
'\"_info\":\"// This is a generated file; do not edit or check into version control.\",'
'\"dependencyGraph\":['
'['
'{'
'\"name\":\"plugin_a\",'
'\"dependencies\":[\"plugin_b\",\"plugin_c\"]'
......@@ -193,14 +194,12 @@ public class DummyPluginAClass {
'\"name\":\"plugin_c\",'
'\"dependencies\":[]'
'}'
']'
'}';
if (flutterPluginsDependenciesFileContent != kExpectedPluginsDependenciesContent) {
']';
final String graphString = json.encode(dependencyGraph);
if (graphString != kExpectedPluginsDependenciesContent) {
return TaskResult.failure(
'Unexpected file content in ${flutterPluginsDependenciesFile.path}: '
'Found "$flutterPluginsDependenciesFileContent" instead of '
'"$kExpectedPluginsDependenciesContent"'
'Found "$graphString" instead of "$kExpectedPluginsDependenciesContent"'
);
}
......
......@@ -11,6 +11,7 @@ import 'package:yaml/yaml.dart';
import 'android/gradle.dart';
import 'base/common.dart';
import 'base/file_system.dart';
import 'base/time.dart';
import 'convert.dart';
import 'dart/package_map.dart';
import 'features.dart';
......@@ -18,6 +19,7 @@ import 'globals.dart' as globals;
import 'macos/cocoapods.dart';
import 'platform_plugins.dart';
import 'project.dart';
import 'version.dart';
void _renderTemplateToFile(String template, dynamic context, String filePath) {
final String renderedTemplate =
......@@ -263,7 +265,7 @@ class Plugin {
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'));
if (!globals.fs.isFileSync(pubspecPath)) {
return null;
......@@ -302,7 +304,7 @@ List<Plugin> findPlugins(FlutterProject project) {
}
packages.forEach((String name, Uri uri) {
final Uri packageRoot = uri.resolve('..');
final Plugin plugin = _pluginFromPubspec(name, packageRoot);
final Plugin plugin = _pluginFromPackage(name, packageRoot);
if (plugin != null) {
plugins.add(plugin);
}
......@@ -310,55 +312,159 @@ List<Plugin> findPlugins(FlutterProject project) {
return plugins;
}
/// Writes the .flutter-plugins and .flutter-plugins-dependencies files based on the list of plugins.
/// If there aren't any plugins, then the files aren't written to disk.
/// Filters [plugins] to those supported by [platformKey].
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,
///
/// Finally, returns [true] if .flutter-plugins-dependencies has changed,
/// otherwise returns [false].
bool _writeFlutterPluginsList(FlutterProject project, List<Plugin> plugins) {
final File pluginsFile = project.flutterPluginsDependenciesFile;
if (plugins.isEmpty) {
if (pluginsFile.existsSync()) {
pluginsFile.deleteSync();
return true;
}
return false;
}
final String iosKey = project.ios.pluginConfigKey;
final String androidKey = project.android.pluginConfigKey;
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;
final String oldPluginFileContent = _readFileContent(pluginsFile);
final String pluginFileContent = json.encode(result);
pluginsFile.writeAsStringSync(pluginFileContent, flush: true);
return oldPluginFileContent != pluginFileContent;
}
List<dynamic> _createPluginLegacyDependencyGraph(List<Plugin> plugins) {
final List<dynamic> directAppDependencies = <dynamic>[];
const String info = 'This is a generated file; do not edit or check into version control.';
final StringBuffer flutterPluginsBuffer = StringBuffer('# $info\n');
final Set<String> pluginNames = <String>{};
final Set<String> pluginNames = plugins.map((Plugin plugin) => plugin.name).toSet();
for (final Plugin plugin in plugins) {
pluginNames.add(plugin.name);
}
for (final Plugin plugin in plugins) {
flutterPluginsBuffer.write('${plugin.name}=${fsUtils.escapePath(plugin.path)}\n');
directAppDependencies.add(<String, dynamic>{
'name': plugin.name,
// Extract the plugin dependencies which happen to be plugins.
'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 String oldPluginFileContent = _readFileContent(pluginsFile);
final String pluginFileContent = flutterPluginsBuffer.toString();
if (pluginNames.isNotEmpty) {
pluginsFile.writeAsStringSync(pluginFileContent, flush: true);
} else {
if (plugins.isEmpty) {
if (pluginsFile.existsSync()) {
pluginsFile.deleteSync();
return true;
}
return false;
}
final File dependenciesFile = project.flutterPluginsDependenciesFile;
final String oldDependenciesFileContent = _readFileContent(dependenciesFile);
final String dependenciesFileContent = json.encode(<String, dynamic>{
'_info': '// $info',
'dependencyGraph': directAppDependencies,
});
if (pluginNames.isNotEmpty) {
dependenciesFile.writeAsStringSync(dependenciesFileContent, flush: true);
} else {
if (dependenciesFile.existsSync()) {
dependenciesFile.deleteSync();
}
const String info = 'This is a generated file; do not edit or check into version control.';
final StringBuffer flutterPluginsBuffer = StringBuffer('# $info\n');
for (final Plugin plugin in plugins) {
flutterPluginsBuffer.write('${plugin.name}=${fsUtils.escapePath(plugin.path)}\n');
}
final String oldPluginFileContent = _readFileContent(pluginsFile);
final String pluginFileContent = flutterPluginsBuffer.toString();
pluginsFile.writeAsStringSync(pluginFileContent, flush: true);
return oldPluginFileContent != _readFileContent(pluginsFile)
|| oldDependenciesFileContent != _readFileContent(dependenciesFile);
return oldPluginFileContent != _readFileContent(pluginsFile);
}
/// Returns the contents of [File] or [null] if that file does not exist.
......@@ -782,8 +888,13 @@ Future<void> _writeWebPluginRegistrant(FlutterProject project, List<Plugin> plug
/// Assumes `pub get` has been executed since last change to `pubspec.yaml`.
void refreshPluginsList(FlutterProject project, {bool checkProjects = false}) {
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);
if (changed) {
if (changed || legacyChanged) {
if (!checkProjects || project.ios.existsSync()) {
cocoaPods.invalidatePodInstallOutput(project.ios);
}
......
......@@ -20,6 +20,7 @@ import 'flutter_manifest.dart';
import 'globals.dart' as globals;
import 'ios/plist_parser.dart';
import 'ios/xcodeproj.dart' as xcode;
import 'platform_plugins.dart';
import 'plugins.dart';
import 'template.dart';
......@@ -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.
///
/// This defines interfaces common to iOS and macOS projects.
......@@ -300,12 +311,15 @@ abstract class XcodeBasedProject {
///
/// Instances will reflect the contents of the `ios/` sub-folder of
/// 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);
@override
final FlutterProject parent;
@override
String get pluginConfigKey => IOSPlugin.kConfigKey;
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 _hostAppBundleName = 'Runner';
......@@ -574,12 +588,15 @@ class IosProject implements XcodeBasedProject {
///
/// Instances will reflect the contents of the `android/` sub-folder of
/// Flutter applications and the `.android/` sub-folder of Flutter module projects.
class AndroidProject {
class AndroidProject extends FlutterProjectPlatform {
AndroidProject._(this.parent);
/// The parent of this project.
final FlutterProject parent;
@override
String get pluginConfigKey => AndroidPlugin.kConfigKey;
static final RegExp _applicationIdPattern = RegExp('^\\s*applicationId\\s+[\'\"](.*)[\'\"]\\s*\$');
static final RegExp _kotlinPluginPattern = RegExp('^\\s*apply plugin\:\\s+[\'\"]kotlin-android[\'\"]\\s*\$');
static final RegExp _groupPattern = RegExp('^\\s*group\\s+[\'\"](.*)[\'\"]\\s*\$');
......@@ -627,6 +644,7 @@ class AndroidProject {
}
/// Whether the current flutter project has an Android sub-project.
@override
bool existsSync() {
return parent.isModule || _editableHostAppDirectory.existsSync();
}
......@@ -760,12 +778,16 @@ enum AndroidEmbeddingVersion {
}
/// Represents the web sub-project of a Flutter project.
class WebProject {
class WebProject extends FlutterProjectPlatform {
WebProject._(this.parent);
final FlutterProject parent;
@override
String get pluginConfigKey => WebPlugin.kConfigKey;
/// Whether this flutter project has a web sub-project.
@override
bool existsSync() {
return parent.directory.childDirectory('web').existsSync()
&& indexFile.existsSync();
......@@ -810,12 +832,15 @@ Match _firstMatchInFile(File file, RegExp regExp) {
}
/// The macOS sub project.
class MacOSProject implements XcodeBasedProject {
class MacOSProject extends FlutterProjectPlatform implements XcodeBasedProject {
MacOSProject._(this.parent);
@override
final FlutterProject parent;
@override
String get pluginConfigKey => MacOSPlugin.kConfigKey;
static const String _hostAppBundleName = 'Runner';
@override
......@@ -895,11 +920,15 @@ class MacOSProject implements XcodeBasedProject {
}
/// The Windows sub project
class WindowsProject {
class WindowsProject extends FlutterProjectPlatform {
WindowsProject._(this.project);
final FlutterProject project;
@override
String get pluginConfigKey => WindowsPlugin.kConfigKey;
@override
bool existsSync() => _editableDirectory.existsSync();
Directory get _editableDirectory => project.directory.childDirectory('windows');
......@@ -933,11 +962,14 @@ class WindowsProject {
}
/// The Linux sub project.
class LinuxProject {
class LinuxProject extends FlutterProjectPlatform {
LinuxProject._(this.project);
final FlutterProject project;
@override
String get pluginConfigKey => LinuxPlugin.kConfigKey;
Directory get _editableDirectory => project.directory.childDirectory('linux');
/// The directory in the project that is managed by Flutter. As much as
......@@ -950,6 +982,7 @@ class LinuxProject {
/// checked in should live here.
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
@override
bool existsSync() => _editableDirectory.existsSync();
/// The Linux project makefile.
......
......@@ -16,6 +16,8 @@ import 'cache.dart';
import 'convert.dart';
import 'globals.dart' as globals;
FlutterVersion get flutterVersion => context.get<FlutterVersion>();
class FlutterVersion {
FlutterVersion([this._clock = const SystemClock()]) {
_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