mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
427 lines
14 KiB
Dart
427 lines
14 KiB
Dart
// 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:pub_semver/pub_semver.dart';
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
import 'base/common.dart';
|
|
import 'base/file_system.dart';
|
|
import 'platform_plugins.dart';
|
|
|
|
class Plugin {
|
|
Plugin({
|
|
required this.name,
|
|
required this.path,
|
|
required this.platforms,
|
|
required this.defaultPackagePlatforms,
|
|
required this.pluginDartClassPlatforms,
|
|
this.flutterConstraint,
|
|
required this.dependencies,
|
|
required this.isDirectDependency,
|
|
this.implementsPackage,
|
|
});
|
|
|
|
/// Parses [Plugin] specification from the provided pluginYaml.
|
|
///
|
|
/// This currently supports two formats. Legacy and Multi-platform.
|
|
///
|
|
/// Example of the deprecated Legacy format.
|
|
///
|
|
/// flutter:
|
|
/// plugin:
|
|
/// androidPackage: io.flutter.plugins.sample
|
|
/// iosPrefix: FLT
|
|
/// pluginClass: SamplePlugin
|
|
///
|
|
/// Example Multi-platform format.
|
|
///
|
|
/// flutter:
|
|
/// plugin:
|
|
/// platforms:
|
|
/// android:
|
|
/// package: io.flutter.plugins.sample
|
|
/// pluginClass: SamplePlugin
|
|
/// ios:
|
|
/// # A plugin implemented through method channels.
|
|
/// pluginClass: SamplePlugin
|
|
/// linux:
|
|
/// # A plugin implemented purely in Dart code.
|
|
/// dartPluginClass: SamplePlugin
|
|
/// macos:
|
|
/// # A plugin implemented with `dart:ffi`.
|
|
/// ffiPlugin: true
|
|
/// windows:
|
|
/// # A plugin using platform-specific Dart and method channels.
|
|
/// dartPluginClass: SamplePlugin
|
|
/// pluginClass: SamplePlugin
|
|
factory Plugin.fromYaml(
|
|
String name,
|
|
String path,
|
|
YamlMap? pluginYaml,
|
|
VersionConstraint? flutterConstraint,
|
|
List<String> dependencies, {
|
|
required FileSystem fileSystem,
|
|
Set<String>? appDependencies,
|
|
}) {
|
|
final List<String> errors = validatePluginYaml(pluginYaml);
|
|
if (errors.isNotEmpty) {
|
|
throwToolExit('Invalid plugin specification $name.\n${errors.join('\n')}');
|
|
}
|
|
if (pluginYaml != null && pluginYaml['platforms'] != null) {
|
|
return Plugin._fromMultiPlatformYaml(
|
|
name,
|
|
path,
|
|
pluginYaml,
|
|
flutterConstraint,
|
|
dependencies,
|
|
fileSystem,
|
|
appDependencies != null && appDependencies.contains(name),
|
|
);
|
|
}
|
|
return Plugin._fromLegacyYaml(
|
|
name,
|
|
path,
|
|
pluginYaml,
|
|
flutterConstraint,
|
|
dependencies,
|
|
fileSystem,
|
|
appDependencies != null && appDependencies.contains(name),
|
|
);
|
|
}
|
|
|
|
factory Plugin._fromMultiPlatformYaml(
|
|
String name,
|
|
String path,
|
|
YamlMap pluginYaml,
|
|
VersionConstraint? flutterConstraint,
|
|
List<String> dependencies,
|
|
FileSystem fileSystem,
|
|
bool isDirectDependency,
|
|
) {
|
|
assert (pluginYaml['platforms'] != null, 'Invalid multi-platform plugin specification $name.');
|
|
final YamlMap platformsYaml = pluginYaml['platforms'] as YamlMap;
|
|
|
|
assert (_validateMultiPlatformYaml(platformsYaml).isEmpty,
|
|
'Invalid multi-platform plugin specification $name.');
|
|
|
|
final Map<String, PluginPlatform> platforms = <String, PluginPlatform>{};
|
|
|
|
if (_providesImplementationForPlatform(platformsYaml, AndroidPlugin.kConfigKey)) {
|
|
platforms[AndroidPlugin.kConfigKey] = AndroidPlugin.fromYaml(
|
|
name,
|
|
platformsYaml[AndroidPlugin.kConfigKey] as YamlMap,
|
|
path,
|
|
fileSystem,
|
|
);
|
|
}
|
|
|
|
if (_providesImplementationForPlatform(platformsYaml, IOSPlugin.kConfigKey)) {
|
|
platforms[IOSPlugin.kConfigKey] =
|
|
IOSPlugin.fromYaml(name, platformsYaml[IOSPlugin.kConfigKey] as YamlMap);
|
|
}
|
|
|
|
if (_providesImplementationForPlatform(platformsYaml, LinuxPlugin.kConfigKey)) {
|
|
platforms[LinuxPlugin.kConfigKey] =
|
|
LinuxPlugin.fromYaml(name, platformsYaml[LinuxPlugin.kConfigKey] as YamlMap);
|
|
}
|
|
|
|
if (_providesImplementationForPlatform(platformsYaml, MacOSPlugin.kConfigKey)) {
|
|
platforms[MacOSPlugin.kConfigKey] =
|
|
MacOSPlugin.fromYaml(name, platformsYaml[MacOSPlugin.kConfigKey] as YamlMap);
|
|
}
|
|
|
|
if (_providesImplementationForPlatform(platformsYaml, WebPlugin.kConfigKey)) {
|
|
platforms[WebPlugin.kConfigKey] =
|
|
WebPlugin.fromYaml(name, platformsYaml[WebPlugin.kConfigKey] as YamlMap);
|
|
}
|
|
|
|
if (_providesImplementationForPlatform(platformsYaml, WindowsPlugin.kConfigKey)) {
|
|
platforms[WindowsPlugin.kConfigKey] =
|
|
WindowsPlugin.fromYaml(name, platformsYaml[WindowsPlugin.kConfigKey] as YamlMap);
|
|
}
|
|
|
|
// TODO(stuartmorgan): Consider merging web into this common handling; the
|
|
// fact that its implementation of Dart-only plugins and default packages
|
|
// are separate is legacy.
|
|
final List<String> sharedHandlingPlatforms = <String>[
|
|
AndroidPlugin.kConfigKey,
|
|
IOSPlugin.kConfigKey,
|
|
LinuxPlugin.kConfigKey,
|
|
MacOSPlugin.kConfigKey,
|
|
WindowsPlugin.kConfigKey,
|
|
];
|
|
final Map<String, String> defaultPackages = <String, String>{};
|
|
final Map<String, String> dartPluginClasses = <String, String>{};
|
|
for (final String platform in sharedHandlingPlatforms) {
|
|
final String? defaultPackage = _getDefaultPackageForPlatform(platformsYaml, platform);
|
|
if (defaultPackage != null) {
|
|
defaultPackages[platform] = defaultPackage;
|
|
}
|
|
final String? dartClass = _getPluginDartClassForPlatform(platformsYaml, platform);
|
|
if (dartClass != null) {
|
|
dartPluginClasses[platform] = dartClass;
|
|
}
|
|
}
|
|
|
|
return Plugin(
|
|
name: name,
|
|
path: path,
|
|
platforms: platforms,
|
|
defaultPackagePlatforms: defaultPackages,
|
|
pluginDartClassPlatforms: dartPluginClasses,
|
|
flutterConstraint: flutterConstraint,
|
|
dependencies: dependencies,
|
|
isDirectDependency: isDirectDependency,
|
|
implementsPackage: pluginYaml['implements'] != null ? pluginYaml['implements'] as String : '',
|
|
);
|
|
}
|
|
|
|
factory Plugin._fromLegacyYaml(
|
|
String name,
|
|
String path,
|
|
dynamic pluginYaml,
|
|
VersionConstraint? flutterConstraint,
|
|
List<String> dependencies,
|
|
FileSystem fileSystem,
|
|
bool isDirectDependency,
|
|
) {
|
|
final Map<String, PluginPlatform> platforms = <String, PluginPlatform>{};
|
|
final String? pluginClass = (pluginYaml as Map<dynamic, dynamic>)['pluginClass'] as String?;
|
|
if (pluginClass != null) {
|
|
final String? androidPackage = pluginYaml['androidPackage'] as String?;
|
|
if (androidPackage != null) {
|
|
platforms[AndroidPlugin.kConfigKey] = AndroidPlugin(
|
|
name: name,
|
|
package: androidPackage,
|
|
pluginClass: pluginClass,
|
|
pluginPath: path,
|
|
fileSystem: fileSystem,
|
|
);
|
|
}
|
|
|
|
final String iosPrefix = pluginYaml['iosPrefix'] as String? ?? '';
|
|
platforms[IOSPlugin.kConfigKey] =
|
|
IOSPlugin(
|
|
name: name,
|
|
classPrefix: iosPrefix,
|
|
pluginClass: pluginClass,
|
|
);
|
|
}
|
|
return Plugin(
|
|
name: name,
|
|
path: path,
|
|
platforms: platforms,
|
|
defaultPackagePlatforms: <String, String>{},
|
|
pluginDartClassPlatforms: <String, String>{},
|
|
flutterConstraint: flutterConstraint,
|
|
dependencies: dependencies,
|
|
isDirectDependency: isDirectDependency,
|
|
);
|
|
}
|
|
|
|
/// Create a YamlMap that represents the supported platforms.
|
|
///
|
|
/// For example, if the `platforms` contains 'ios' and 'android', the return map looks like:
|
|
///
|
|
/// android:
|
|
/// package: io.flutter.plugins.sample
|
|
/// pluginClass: SamplePlugin
|
|
/// ios:
|
|
/// pluginClass: SamplePlugin
|
|
static YamlMap createPlatformsYamlMap(List<String> platforms, String pluginClass, String androidPackage) {
|
|
final Map<String, dynamic> map = <String, dynamic>{};
|
|
for (final String platform in platforms) {
|
|
map[platform] = <String, String>{
|
|
'pluginClass': pluginClass,
|
|
...platform == 'android' ? <String, String>{'package': androidPackage} : <String, String>{},
|
|
};
|
|
}
|
|
return YamlMap.wrap(map);
|
|
}
|
|
|
|
static List<String> validatePluginYaml(YamlMap? yaml) {
|
|
if (yaml == null) {
|
|
return <String>['Invalid "plugin" specification.'];
|
|
}
|
|
|
|
final bool usesOldPluginFormat = const <String>{
|
|
'androidPackage',
|
|
'iosPrefix',
|
|
'pluginClass',
|
|
}.any(yaml.containsKey);
|
|
|
|
final bool usesNewPluginFormat = yaml.containsKey('platforms');
|
|
|
|
if (usesOldPluginFormat && usesNewPluginFormat) {
|
|
const String errorMessage =
|
|
'The flutter.plugin.platforms key cannot be used in combination with the old '
|
|
'flutter.plugin.{androidPackage,iosPrefix,pluginClass} keys. '
|
|
'See: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin';
|
|
return <String>[errorMessage];
|
|
}
|
|
|
|
if (!usesOldPluginFormat && !usesNewPluginFormat) {
|
|
const String errorMessage =
|
|
'Cannot find the `flutter.plugin.platforms` key in the `pubspec.yaml` file. '
|
|
'An instruction to format the `pubspec.yaml` can be found here: '
|
|
'https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms';
|
|
return <String>[errorMessage];
|
|
}
|
|
|
|
if (usesNewPluginFormat) {
|
|
if (yaml['platforms'] != null && yaml['platforms'] is! YamlMap) {
|
|
const String errorMessage = 'flutter.plugin.platforms should be a map with the platform name as the key';
|
|
return <String>[errorMessage];
|
|
}
|
|
return _validateMultiPlatformYaml(yaml['platforms'] as YamlMap?);
|
|
} else {
|
|
return _validateLegacyYaml(yaml);
|
|
}
|
|
}
|
|
|
|
static List<String> _validateMultiPlatformYaml(YamlMap? yaml) {
|
|
bool isInvalid(String key, bool Function(YamlMap) validate) {
|
|
if (!yaml!.containsKey(key)) {
|
|
return false;
|
|
}
|
|
final dynamic yamlValue = yaml[key];
|
|
if (yamlValue is! YamlMap) {
|
|
return true;
|
|
}
|
|
if (yamlValue.containsKey('default_package')) {
|
|
return false;
|
|
}
|
|
return !validate(yamlValue);
|
|
}
|
|
|
|
if (yaml == null) {
|
|
return <String>['Invalid "platforms" specification.'];
|
|
}
|
|
final List<String> errors = <String>[];
|
|
if (isInvalid(AndroidPlugin.kConfigKey, AndroidPlugin.validate)) {
|
|
errors.add('Invalid "android" plugin specification.');
|
|
}
|
|
if (isInvalid(IOSPlugin.kConfigKey, IOSPlugin.validate)) {
|
|
errors.add('Invalid "ios" plugin specification.');
|
|
}
|
|
if (isInvalid(LinuxPlugin.kConfigKey, LinuxPlugin.validate)) {
|
|
errors.add('Invalid "linux" plugin specification.');
|
|
}
|
|
if (isInvalid(MacOSPlugin.kConfigKey, MacOSPlugin.validate)) {
|
|
errors.add('Invalid "macos" plugin specification.');
|
|
}
|
|
if (isInvalid(WindowsPlugin.kConfigKey, WindowsPlugin.validate)) {
|
|
errors.add('Invalid "windows" plugin specification.');
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
static List<String> _validateLegacyYaml(YamlMap yaml) {
|
|
final List<String> errors = <String>[];
|
|
|
|
if (yaml['androidPackage'] != null && yaml['androidPackage'] is! String) {
|
|
errors.add('The "androidPackage" must either be null or a string.');
|
|
}
|
|
if (yaml['iosPrefix'] != null && yaml['iosPrefix'] is! String) {
|
|
errors.add('The "iosPrefix" must either be null or a string.');
|
|
}
|
|
if (yaml['pluginClass'] != null && yaml['pluginClass'] is! String) {
|
|
errors.add('The "pluginClass" must either be null or a string..');
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
static bool _supportsPlatform(YamlMap platformsYaml, String platformKey) {
|
|
if (!platformsYaml.containsKey(platformKey)) {
|
|
return false;
|
|
}
|
|
if (platformsYaml[platformKey] is YamlMap) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static String? _getDefaultPackageForPlatform(YamlMap platformsYaml, String platformKey) {
|
|
if (!_supportsPlatform(platformsYaml, platformKey)) {
|
|
return null;
|
|
}
|
|
if ((platformsYaml[platformKey] as YamlMap).containsKey(kDefaultPackage)) {
|
|
return (platformsYaml[platformKey] as YamlMap)[kDefaultPackage] as String;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static String? _getPluginDartClassForPlatform(YamlMap platformsYaml, String platformKey) {
|
|
if (!_supportsPlatform(platformsYaml, platformKey)) {
|
|
return null;
|
|
}
|
|
if ((platformsYaml[platformKey] as YamlMap).containsKey(kDartPluginClass)) {
|
|
return (platformsYaml[platformKey] as YamlMap)[kDartPluginClass] as String;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static bool _providesImplementationForPlatform(YamlMap platformsYaml, String platformKey) {
|
|
if (!_supportsPlatform(platformsYaml, platformKey)) {
|
|
return false;
|
|
}
|
|
if ((platformsYaml[platformKey] as YamlMap).containsKey(kDefaultPackage)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
final String name;
|
|
final String path;
|
|
|
|
/// The name of the interface package that this plugin implements.
|
|
/// If [null], this plugin doesn't implement an interface.
|
|
final String? implementsPackage;
|
|
|
|
/// The required version of Flutter, if specified.
|
|
final VersionConstraint? flutterConstraint;
|
|
|
|
/// The name of the packages this plugin depends on.
|
|
final List<String> dependencies;
|
|
|
|
/// This is a mapping from platform config key to the plugin platform spec.
|
|
final Map<String, PluginPlatform> platforms;
|
|
|
|
/// This is a mapping from platform config key to the default package implementation.
|
|
final Map<String, String> defaultPackagePlatforms;
|
|
|
|
/// This is a mapping from platform config key to the plugin class for the given platform.
|
|
final Map<String, String> pluginDartClassPlatforms;
|
|
|
|
/// Whether this plugin is a direct dependency of the app.
|
|
/// If [false], the plugin is a dependency of another plugin.
|
|
final bool isDirectDependency;
|
|
}
|
|
|
|
/// Metadata associated with the resolution of a platform interface of a plugin.
|
|
class PluginInterfaceResolution {
|
|
PluginInterfaceResolution({
|
|
required this.plugin,
|
|
required this.platform,
|
|
});
|
|
|
|
/// The plugin.
|
|
final Plugin plugin;
|
|
// The name of the platform that this plugin implements.
|
|
final String platform;
|
|
|
|
Map<String, String> toMap() {
|
|
return <String, String> {
|
|
'pluginName': plugin.name,
|
|
'platform': platform,
|
|
'dartClass': plugin.pluginDartClassPlatforms[platform] ?? '',
|
|
};
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return '<PluginInterfaceResolution ${plugin.name} for $platform>';
|
|
}
|
|
}
|