plugins.dart 8.21 KB
Newer Older
1 2 3 4
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:meta/meta.dart';
6
import 'package:mustache/mustache.dart' as mustache;
7 8 9 10 11
import 'package:yaml/yaml.dart';

import 'base/file_system.dart';
import 'dart/package_map.dart';
import 'globals.dart';
12
import 'ios/cocoapods.dart';
13

14 15 16 17
class Plugin {
  final String name;
  final String path;
  final String androidPackage;
18 19
  final String iosPrefix;
  final String pluginClass;
20

21 22 23 24 25 26 27
  Plugin({
    this.name,
    this.path,
    this.androidPackage,
    this.iosPrefix,
    this.pluginClass,
  });
28 29 30

  factory Plugin.fromYaml(String name, String path, dynamic pluginYaml) {
    String androidPackage;
31
    String iosPrefix;
32 33 34
    String pluginClass;
    if (pluginYaml != null) {
      androidPackage = pluginYaml['androidPackage'];
35
      iosPrefix = pluginYaml['iosPrefix'] ?? '';
36 37
      pluginClass = pluginYaml['pluginClass'];
    }
38 39 40 41 42 43 44
    return new Plugin(
      name: name,
      path: path,
      androidPackage: androidPackage,
      iosPrefix: iosPrefix,
      pluginClass: pluginClass,
    );
45 46 47 48
  }
}

Plugin _pluginFromPubspec(String name, Uri packageRoot) {
49
  final String pubspecPath = fs.path.fromUri(packageRoot.resolve('pubspec.yaml'));
50
  if (!fs.isFileSync(pubspecPath))
51
    return null;
52 53 54 55 56 57
  final dynamic pubspec = loadYaml(fs.file(pubspecPath).readAsStringSync());
  if (pubspec == null)
    return null;
  final dynamic flutterConfig = pubspec['flutter'];
  if (flutterConfig == null || !flutterConfig.containsKey('plugin'))
    return null;
58 59 60
  final String packageRootPath = fs.path.fromUri(packageRoot);
  printTrace('Found plugin $name at $packageRootPath');
  return new Plugin.fromYaml(name, packageRootPath, flutterConfig['plugin']);
61 62
}

63 64
List<Plugin> _findPlugins(String directory) {
  final List<Plugin> plugins = <Plugin>[];
65 66
  Map<String, Uri> packages;
  try {
67 68
    final String packagesFile = fs.path.join(directory, PackageMap.globalPackagesPath);
    packages = new PackageMap(packagesFile).map;
69
  } on FormatException catch (e) {
70
    printTrace('Invalid .packages file: $e');
71
    return plugins;
72 73 74
  }
  packages.forEach((String name, Uri uri) {
    final Uri packageRoot = uri.resolve('..');
75 76 77
    final Plugin plugin = _pluginFromPubspec(name, packageRoot);
    if (plugin != null)
      plugins.add(plugin);
78
  });
79
  return plugins;
80 81
}

82 83
/// Returns true if .flutter-plugins has changed, otherwise returns false.
bool _writeFlutterPluginsList(String directory, List<Plugin> plugins) {
84 85
  final File pluginsFile = fs.file(fs.path.join(directory, '.flutter-plugins'));
  final String oldContents = _readFlutterPluginsList(directory);
86
  final String pluginManifest =
87
      plugins.map((Plugin p) => '${p.name}=${escapePath(p.path)}').join('\n');
88
  if (pluginManifest.isNotEmpty) {
89
    pluginsFile.writeAsStringSync('$pluginManifest\n', flush: true);
90
  } else {
91 92
    if (pluginsFile.existsSync())
      pluginsFile.deleteSync();
93
  }
94 95 96 97 98 99 100 101 102
  final String newContents = _readFlutterPluginsList(directory);
  return oldContents != newContents;
}

/// Returns the contents of the `.flutter-plugins` file in [directory], or
/// null if that file does not exist.
String _readFlutterPluginsList(String directory) {
  final File pluginsFile = fs.file(fs.path.join(directory, '.flutter-plugins'));
  return pluginsFile.existsSync() ? pluginsFile.readAsStringSync() : null;
103
}
104 105 106

const String _androidPluginRegistryTemplate = '''package io.flutter.plugins;

107
import io.flutter.plugin.common.PluginRegistry;
108
{{#plugins}}
109 110 111 112 113 114
import {{package}}.{{class}};
{{/plugins}}

/**
 * Generated file. Do not edit.
 */
115 116
public final class GeneratedPluginRegistrant {
  public static void registerWith(PluginRegistry registry) {
117 118 119
    if (alreadyRegisteredWith(registry)) {
      return;
    }
120
{{#plugins}}
121
    {{class}}.registerWith(registry.registrarFor("{{package}}.{{class}}"));
122
{{/plugins}}
123
  }
124 125 126 127 128 129 130 131 132

  private static boolean alreadyRegisteredWith(PluginRegistry registry) {
    final String key = GeneratedPluginRegistrant.class.getCanonicalName();
    if (registry.hasPlugin(key)) {
      return true;
    }
    registry.registrarFor(key);
    return false;
  }
133 134 135
}
''';

136
void _writeAndroidPluginRegistrant(String directory, List<Plugin> plugins) {
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
  final List<Map<String, dynamic>> androidPlugins = plugins
      .where((Plugin p) => p.androidPackage != null && p.pluginClass != null)
      .map((Plugin p) => <String, dynamic>{
          'name': p.name,
          'package': p.androidPackage,
          'class': p.pluginClass,
      })
      .toList();
  final Map<String, dynamic> context = <String, dynamic>{
    'plugins': androidPlugins,
  };

  final String pluginRegistry =
      new mustache.Template(_androidPluginRegistryTemplate).renderString(context);
  final String javaSourcePath = fs.path.join(directory, 'android', 'app', 'src', 'main', 'java');
  final Directory registryDirectory =
      fs.directory(fs.path.join(javaSourcePath, 'io', 'flutter', 'plugins'));
  registryDirectory.createSync(recursive: true);
155
  final File registryFile = registryDirectory.childFile('GeneratedPluginRegistrant.java');
156 157 158 159 160 161 162
  registryFile.writeAsStringSync(pluginRegistry);
}

const String _iosPluginRegistryHeaderTemplate = '''//
//  Generated file. Do not edit.
//

163 164
#ifndef GeneratedPluginRegistrant_h
#define GeneratedPluginRegistrant_h
165 166 167

#import <Flutter/Flutter.h>

168 169
@interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
170 171
@end

172
#endif /* GeneratedPluginRegistrant_h */
173 174 175 176 177 178
''';

const String _iosPluginRegistryImplementationTemplate = '''//
//  Generated file. Do not edit.
//

179
#import "GeneratedPluginRegistrant.h"
180 181 182
{{#plugins}}
#import <{{name}}/{{class}}.h>
{{/plugins}}
183

184
@implementation GeneratedPluginRegistrant
185

186
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
187
{{#plugins}}
188
  [{{prefix}}{{class}} registerWithRegistrar:[registry registrarForPlugin:@"{{prefix}}{{class}}"]];
189 190 191 192 193 194
{{/plugins}}
}

@end
''';

195
void _writeIOSPluginRegistrant(String directory, List<Plugin> plugins) {
196 197 198 199
  final List<Map<String, dynamic>> iosPlugins = plugins
      .where((Plugin p) => p.pluginClass != null)
      .map((Plugin p) => <String, dynamic>{
    'name': p.name,
200
    'prefix': p.iosPrefix,
201 202 203 204 205 206 207 208 209 210 211 212 213
    'class': p.pluginClass,
  }).
  toList();
  final Map<String, dynamic> context = <String, dynamic>{
    'plugins': iosPlugins,
  };

  final String pluginRegistryHeader =
      new mustache.Template(_iosPluginRegistryHeaderTemplate).renderString(context);
  final String pluginRegistryImplementation =
      new mustache.Template(_iosPluginRegistryImplementationTemplate).renderString(context);
  final Directory registryDirectory = fs.directory(fs.path.join(directory, 'ios', 'Runner'));
  registryDirectory.createSync(recursive: true);
214
  final File registryHeaderFile = registryDirectory.childFile('GeneratedPluginRegistrant.h');
215
  registryHeaderFile.writeAsStringSync(pluginRegistryHeader);
216
  final File registryImplementationFile = registryDirectory.childFile('GeneratedPluginRegistrant.m');
217 218 219
  registryImplementationFile.writeAsStringSync(pluginRegistryImplementation);
}

220 221 222 223 224 225 226 227 228 229 230
class InjectPluginsResult{
  InjectPluginsResult({
    @required this.hasPlugin,
    @required this.hasChanged,
  });
  /// True if any flutter plugin exists, otherwise false.
  final bool hasPlugin;
  /// True if plugins have changed since last build.
  final bool hasChanged;
}

231 232
/// Injects plugins found in `pubspec.yaml` into the platform-specific projects.
void injectPlugins({String directory}) {
233 234
  directory ??= fs.currentDirectory.path;
  final List<Plugin> plugins = _findPlugins(directory);
235
  final bool changed = _writeFlutterPluginsList(directory, plugins);
236

237
  if (fs.isDirectorySync(fs.path.join(directory, 'android')))
238 239 240
    _writeAndroidPluginRegistrant(directory, plugins);
  if (fs.isDirectorySync(fs.path.join(directory, 'ios'))) {
    _writeIOSPluginRegistrant(directory, plugins);
241
    final CocoaPods cocoaPods = new CocoaPods();
242 243 244 245 246 247 248 249 250 251 252 253
    if (plugins.isNotEmpty)
      cocoaPods.setupPodfile(directory);
    if (changed)
      cocoaPods.invalidatePodInstallOutput(directory);
  }
}

/// Returns whether the Flutter project at the specified [directory]
/// has any plugin dependencies.
bool hasPlugins({String directory}) {
  directory ??= fs.currentDirectory.path;
  return _readFlutterPluginsList(directory) != null;
254
}