plugins.dart 9.71 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:yaml/yaml.dart';

7
import 'base/common.dart';
8
import 'base/file_system.dart';
9
import 'platform_plugins.dart';
10

11
class Plugin {
12
  Plugin({
13 14 15 16
    required this.name,
    required this.path,
    required this.platforms,
    required this.dependencies,
17 18 19
  }) : assert(name != null),
       assert(path != null),
       assert(platforms != null),
20
       assert(dependencies != null);
21

22 23 24
  /// Parses [Plugin] specification from the provided pluginYaml.
  ///
  /// This currently supports two formats. Legacy and Multi-platform.
25
  ///
26 27
  /// Example of the deprecated Legacy format.
  ///
28 29 30 31
  ///     flutter:
  ///      plugin:
  ///        androidPackage: io.flutter.plugins.sample
  ///        iosPrefix: FLT
32
  ///        pluginClass: SamplePlugin
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
  ///
  /// Example Multi-platform format.
  ///
  ///     flutter:
  ///      plugin:
  ///        platforms:
  ///          android:
  ///            package: io.flutter.plugins.sample
  ///            pluginClass: SamplePlugin
  ///          ios:
  ///            pluginClass: SamplePlugin
  ///          linux:
  ///            pluginClass: SamplePlugin
  ///          macos:
  ///            pluginClass: SamplePlugin
  ///          windows:
  ///            pluginClass: SamplePlugin
50 51 52
  factory Plugin.fromYaml(
    String name,
    String path,
53
    YamlMap? pluginYaml,
54
    List<String> dependencies, {
55
    required FileSystem fileSystem,
56
  }) {
57 58
    final List<String> errors = validatePluginYaml(pluginYaml);
    if (errors.isNotEmpty) {
59
      throwToolExit('Invalid plugin specification $name.\n${errors.join('\n')}');
60 61
    }
    if (pluginYaml != null && pluginYaml['platforms'] != null) {
62
      return Plugin._fromMultiPlatformYaml(name, path, pluginYaml, dependencies, fileSystem);
63
    }
64
    return Plugin._fromLegacyYaml(name, path, pluginYaml, dependencies, fileSystem);
65 66
  }

67 68 69 70 71
  factory Plugin._fromMultiPlatformYaml(
    String name,
    String path,
    dynamic pluginYaml,
    List<String> dependencies,
72
    FileSystem fileSystem,
73
  ) {
74
    assert (pluginYaml != null && pluginYaml['platforms'] != null,
75
            'Invalid multi-platform plugin specification $name.');
76
    final YamlMap platformsYaml = pluginYaml['platforms'] as YamlMap;
77 78

    assert (_validateMultiPlatformYaml(platformsYaml).isEmpty,
79
            'Invalid multi-platform plugin specification $name.');
80 81 82

    final Map<String, PluginPlatform> platforms = <String, PluginPlatform>{};

83
    if (_providesImplementationForPlatform(platformsYaml, AndroidPlugin.kConfigKey)) {
84 85
      platforms[AndroidPlugin.kConfigKey] = AndroidPlugin.fromYaml(
        name,
86
        platformsYaml[AndroidPlugin.kConfigKey] as YamlMap,
87
        path,
88
        fileSystem,
89
      );
90 91
    }

92
    if (_providesImplementationForPlatform(platformsYaml, IOSPlugin.kConfigKey)) {
93
      platforms[IOSPlugin.kConfigKey] =
94
          IOSPlugin.fromYaml(name, platformsYaml[IOSPlugin.kConfigKey] as YamlMap);
95 96
    }

97
    if (_providesImplementationForPlatform(platformsYaml, LinuxPlugin.kConfigKey)) {
98
      platforms[LinuxPlugin.kConfigKey] =
99
          LinuxPlugin.fromYaml(name, platformsYaml[LinuxPlugin.kConfigKey] as YamlMap);
100 101
    }

102
    if (_providesImplementationForPlatform(platformsYaml, MacOSPlugin.kConfigKey)) {
103
      platforms[MacOSPlugin.kConfigKey] =
104
          MacOSPlugin.fromYaml(name, platformsYaml[MacOSPlugin.kConfigKey] as YamlMap);
105 106
    }

107
    if (_providesImplementationForPlatform(platformsYaml, WebPlugin.kConfigKey)) {
108
      platforms[WebPlugin.kConfigKey] =
109
          WebPlugin.fromYaml(name, platformsYaml[WebPlugin.kConfigKey] as YamlMap);
110 111
    }

112
    if (_providesImplementationForPlatform(platformsYaml, WindowsPlugin.kConfigKey)) {
113
      platforms[WindowsPlugin.kConfigKey] =
114
          WindowsPlugin.fromYaml(name, platformsYaml[WindowsPlugin.kConfigKey] as YamlMap);
115 116
    }

117 118 119 120
    return Plugin(
      name: name,
      path: path,
      platforms: platforms,
121
      dependencies: dependencies,
122 123 124
    );
  }

125 126 127 128 129
  factory Plugin._fromLegacyYaml(
    String name,
    String path,
    dynamic pluginYaml,
    List<String> dependencies,
130
    FileSystem fileSystem,
131
  ) {
132
    final Map<String, PluginPlatform> platforms = <String, PluginPlatform>{};
133
    final String pluginClass = pluginYaml['pluginClass'] as String;
134
    if (pluginYaml != null && pluginClass != null) {
135
      final String androidPackage = pluginYaml['androidPackage'] as String;
136
      if (androidPackage != null) {
137 138
        platforms[AndroidPlugin.kConfigKey] = AndroidPlugin(
          name: name,
139
          package: pluginYaml['androidPackage'] as String,
140 141
          pluginClass: pluginClass,
          pluginPath: path,
142
          fileSystem: fileSystem,
143
        );
144 145
      }

146
      final String iosPrefix = pluginYaml['iosPrefix'] as String? ?? '';
147 148 149 150 151 152
      platforms[IOSPlugin.kConfigKey] =
          IOSPlugin(
            name: name,
            classPrefix: iosPrefix,
            pluginClass: pluginClass,
          );
153
    }
154
    return Plugin(
155 156
      name: name,
      path: path,
157
      platforms: platforms,
158
      dependencies: dependencies,
159
    );
160
  }
161

162 163 164
  /// Create a YamlMap that represents the supported platforms.
  ///
  /// For example, if the `platforms` contains 'ios' and 'android', the return map looks like:
165 166 167 168 169 170
  ///
  ///     android:
  ///       package: io.flutter.plugins.sample
  ///       pluginClass: SamplePlugin
  ///     ios:
  ///       pluginClass: SamplePlugin
171 172 173 174 175 176 177 178 179 180 181
  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);
  }

182
  static List<String> validatePluginYaml(YamlMap? yaml) {
183 184 185
    if (yaml == null) {
      return <String>['Invalid "plugin" specification.'];
    }
186 187 188 189 190 191 192 193 194 195 196

    final bool usesOldPluginFormat = const <String>{
      'androidPackage',
      'iosPrefix',
      'pluginClass',
    }.any(yaml.containsKey);

    final bool usesNewPluginFormat = yaml.containsKey('platforms');

    if (usesOldPluginFormat && usesNewPluginFormat) {
      const String errorMessage =
197 198
          'The flutter.plugin.platforms key cannot be used in combination with the old '
          'flutter.plugin.{androidPackage,iosPrefix,pluginClass} keys. '
199 200 201 202
          'See: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin';
      return <String>[errorMessage];
    }

203 204 205 206 207 208 209 210
    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];
    }

211
    if (usesNewPluginFormat) {
212 213 214 215
      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];
      }
216
      return _validateMultiPlatformYaml(yaml['platforms'] as YamlMap?);
217 218 219 220 221
    } else {
      return _validateLegacyYaml(yaml);
    }
  }

222
  static List<String> _validateMultiPlatformYaml(YamlMap? yaml) {
223
    bool isInvalid(String key, bool Function(YamlMap) validate) {
224
      if (!yaml!.containsKey(key)) {
225 226
        return false;
      }
227 228
      final dynamic yamlValue = yaml[key];
      if (yamlValue is! YamlMap) {
229
        return true;
230
      }
231
      if (yamlValue.containsKey('default_package')) {
232 233
        return false;
      }
234
      return !validate(yamlValue);
235
    }
236 237 238 239

    if (yaml == null) {
      return <String>['Invalid "platforms" specification.'];
    }
240
    final List<String> errors = <String>[];
241
    if (isInvalid(AndroidPlugin.kConfigKey, AndroidPlugin.validate)) {
242 243
      errors.add('Invalid "android" plugin specification.');
    }
244
    if (isInvalid(IOSPlugin.kConfigKey, IOSPlugin.validate)) {
245 246
      errors.add('Invalid "ios" plugin specification.');
    }
247
    if (isInvalid(LinuxPlugin.kConfigKey, LinuxPlugin.validate)) {
248 249
      errors.add('Invalid "linux" plugin specification.');
    }
250
    if (isInvalid(MacOSPlugin.kConfigKey, MacOSPlugin.validate)) {
251 252
      errors.add('Invalid "macos" plugin specification.');
    }
253
    if (isInvalid(WindowsPlugin.kConfigKey, WindowsPlugin.validate)) {
254 255
      errors.add('Invalid "windows" plugin specification.');
    }
256 257 258 259 260
    return errors;
  }

  static List<String> _validateLegacyYaml(YamlMap yaml) {
    final List<String> errors = <String>[];
261

262 263 264 265 266 267 268 269 270 271 272 273
    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;
  }

274
  static bool _providesImplementationForPlatform(YamlMap platformsYaml, String platformKey) {
275
    if (!platformsYaml.containsKey(platformKey)) {
276 277
      return false;
    }
278
    if ((platformsYaml[platformKey] as YamlMap).containsKey('default_package')) {
279 280 281 282 283
      return false;
    }
    return true;
  }

284 285
  final String name;
  final String path;
286

287 288 289
  /// The name of the packages this plugin depends on.
  final List<String> dependencies;

290 291
  /// This is a mapping from platform config key to the plugin platform spec.
  final Map<String, PluginPlatform> platforms;
292
}