312 lines
10 KiB
Dart
312 lines
10 KiB
Dart
// 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.
|
|
|
|
import 'package:meta/meta.dart';
|
|
import 'package:mustache/mustache.dart' as mustache;
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
import 'base/file_system.dart';
|
|
import 'dart/package_map.dart';
|
|
import 'flutter_manifest.dart';
|
|
import 'globals.dart';
|
|
import 'ios/cocoapods.dart';
|
|
|
|
void _renderTemplateToFile(String template, dynamic context, String filePath) {
|
|
final String renderedTemplate =
|
|
new mustache.Template(template).renderString(context);
|
|
final File file = fs.file(filePath);
|
|
file.createSync(recursive: true);
|
|
file.writeAsStringSync(renderedTemplate);
|
|
}
|
|
|
|
class Plugin {
|
|
final String name;
|
|
final String path;
|
|
final String androidPackage;
|
|
final String iosPrefix;
|
|
final String pluginClass;
|
|
|
|
Plugin({
|
|
this.name,
|
|
this.path,
|
|
this.androidPackage,
|
|
this.iosPrefix,
|
|
this.pluginClass,
|
|
});
|
|
|
|
factory Plugin.fromYaml(String name, String path, dynamic pluginYaml) {
|
|
String androidPackage;
|
|
String iosPrefix;
|
|
String pluginClass;
|
|
if (pluginYaml != null) {
|
|
androidPackage = pluginYaml['androidPackage'];
|
|
iosPrefix = pluginYaml['iosPrefix'] ?? '';
|
|
pluginClass = pluginYaml['pluginClass'];
|
|
}
|
|
return new Plugin(
|
|
name: name,
|
|
path: path,
|
|
androidPackage: androidPackage,
|
|
iosPrefix: iosPrefix,
|
|
pluginClass: pluginClass,
|
|
);
|
|
}
|
|
}
|
|
|
|
Plugin _pluginFromPubspec(String name, Uri packageRoot) {
|
|
final String pubspecPath = fs.path.fromUri(packageRoot.resolve('pubspec.yaml'));
|
|
if (!fs.isFileSync(pubspecPath))
|
|
return null;
|
|
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;
|
|
final String packageRootPath = fs.path.fromUri(packageRoot);
|
|
printTrace('Found plugin $name at $packageRootPath');
|
|
return new Plugin.fromYaml(name, packageRootPath, flutterConfig['plugin']);
|
|
}
|
|
|
|
List<Plugin> findPlugins(String directory) {
|
|
final List<Plugin> plugins = <Plugin>[];
|
|
Map<String, Uri> packages;
|
|
try {
|
|
final String packagesFile = fs.path.join(directory, PackageMap.globalPackagesPath);
|
|
packages = new PackageMap(packagesFile).map;
|
|
} on FormatException catch (e) {
|
|
printTrace('Invalid .packages file: $e');
|
|
return plugins;
|
|
}
|
|
packages.forEach((String name, Uri uri) {
|
|
final Uri packageRoot = uri.resolve('..');
|
|
final Plugin plugin = _pluginFromPubspec(name, packageRoot);
|
|
if (plugin != null)
|
|
plugins.add(plugin);
|
|
});
|
|
return plugins;
|
|
}
|
|
|
|
/// Returns true if .flutter-plugins has changed, otherwise returns false.
|
|
bool _writeFlutterPluginsList(String directory, List<Plugin> plugins) {
|
|
final File pluginsFile = fs.file(fs.path.join(directory, '.flutter-plugins'));
|
|
final String oldContents = _readFlutterPluginsList(directory);
|
|
final String pluginManifest =
|
|
plugins.map((Plugin p) => '${p.name}=${escapePath(p.path)}').join('\n');
|
|
if (pluginManifest.isNotEmpty) {
|
|
pluginsFile.writeAsStringSync('$pluginManifest\n', flush: true);
|
|
} else {
|
|
if (pluginsFile.existsSync())
|
|
pluginsFile.deleteSync();
|
|
}
|
|
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;
|
|
}
|
|
|
|
const String _androidPluginRegistryTemplate = '''package io.flutter.plugins;
|
|
|
|
import io.flutter.plugin.common.PluginRegistry;
|
|
{{#plugins}}
|
|
import {{package}}.{{class}};
|
|
{{/plugins}}
|
|
|
|
/**
|
|
* Generated file. Do not edit.
|
|
*/
|
|
public final class GeneratedPluginRegistrant {
|
|
public static void registerWith(PluginRegistry registry) {
|
|
if (alreadyRegisteredWith(registry)) {
|
|
return;
|
|
}
|
|
{{#plugins}}
|
|
{{class}}.registerWith(registry.registrarFor("{{package}}.{{class}}"));
|
|
{{/plugins}}
|
|
}
|
|
|
|
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
|
|
final String key = GeneratedPluginRegistrant.class.getCanonicalName();
|
|
if (registry.hasPlugin(key)) {
|
|
return true;
|
|
}
|
|
registry.registrarFor(key);
|
|
return false;
|
|
}
|
|
}
|
|
''';
|
|
|
|
void _writeAndroidPluginRegistrant(String directory, List<Plugin> plugins) {
|
|
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 javaSourcePath = fs.path.join(directory, 'src', 'main', 'java');
|
|
final String registryPath = fs.path.join(javaSourcePath, 'io', 'flutter', 'plugins', 'GeneratedPluginRegistrant.java');
|
|
_renderTemplateToFile(_androidPluginRegistryTemplate, context, registryPath);
|
|
}
|
|
|
|
const String _iosPluginRegistryHeaderTemplate = '''//
|
|
// Generated file. Do not edit.
|
|
//
|
|
|
|
#ifndef GeneratedPluginRegistrant_h
|
|
#define GeneratedPluginRegistrant_h
|
|
|
|
#import <Flutter/Flutter.h>
|
|
|
|
@interface GeneratedPluginRegistrant : NSObject
|
|
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
|
|
@end
|
|
|
|
#endif /* GeneratedPluginRegistrant_h */
|
|
''';
|
|
|
|
const String _iosPluginRegistryImplementationTemplate = '''//
|
|
// Generated file. Do not edit.
|
|
//
|
|
|
|
#import "GeneratedPluginRegistrant.h"
|
|
{{#plugins}}
|
|
#import <{{name}}/{{class}}.h>
|
|
{{/plugins}}
|
|
|
|
@implementation GeneratedPluginRegistrant
|
|
|
|
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
|
|
{{#plugins}}
|
|
[{{prefix}}{{class}} registerWithRegistrar:[registry registrarForPlugin:@"{{prefix}}{{class}}"]];
|
|
{{/plugins}}
|
|
}
|
|
|
|
@end
|
|
''';
|
|
|
|
const String _iosPluginRegistrantPodspecTemplate = '''
|
|
#
|
|
# Generated file, do not edit.
|
|
#
|
|
|
|
Pod::Spec.new do |s|
|
|
s.name = 'FlutterPluginRegistrant'
|
|
s.version = '0.0.1'
|
|
s.summary = 'Registers plugins with your flutter app'
|
|
s.description = <<-DESC
|
|
Depends on all your plugins, and provides a function to register them.
|
|
DESC
|
|
s.homepage = 'https://flutter.io'
|
|
s.license = { :type => 'BSD' }
|
|
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
|
|
s.ios.deployment_target = '7.0'
|
|
s.source_files = "Classes", "Classes/**/*.{h,m}"
|
|
s.source = { :path => '.' }
|
|
s.public_header_files = './Classes/**/*.h'
|
|
{{#plugins}}
|
|
s.dependency '{{name}}'
|
|
{{/plugins}}
|
|
end
|
|
''';
|
|
|
|
void _writeIOSPluginRegistrant(String directory, FlutterManifest manifest, List<Plugin> plugins) {
|
|
final List<Map<String, dynamic>> iosPlugins = plugins
|
|
.where((Plugin p) => p.pluginClass != null)
|
|
.map((Plugin p) => <String, dynamic>{
|
|
'name': p.name,
|
|
'prefix': p.iosPrefix,
|
|
'class': p.pluginClass,
|
|
}).
|
|
toList();
|
|
final Map<String, dynamic> context = <String, dynamic>{
|
|
'plugins': iosPlugins,
|
|
};
|
|
|
|
if (manifest.isModule) {
|
|
// In a module create the GeneratedPluginRegistrant as a pod to be included
|
|
// from a hosting app.
|
|
final String registryDirectory = fs.path.join(directory, 'Flutter', 'FlutterPluginRegistrant');
|
|
final String registryClassesDirectory = fs.path.join(registryDirectory, 'Classes');
|
|
_renderTemplateToFile(
|
|
_iosPluginRegistrantPodspecTemplate,
|
|
context,
|
|
fs.path.join(registryDirectory, 'FlutterPluginRegistrant.podspec'),
|
|
);
|
|
_renderTemplateToFile(
|
|
_iosPluginRegistryHeaderTemplate,
|
|
context,
|
|
fs.path.join(registryClassesDirectory, 'GeneratedPluginRegistrant.h'),
|
|
);
|
|
_renderTemplateToFile(
|
|
_iosPluginRegistryImplementationTemplate,
|
|
context,
|
|
fs.path.join(registryClassesDirectory, 'GeneratedPluginRegistrant.m'),
|
|
);
|
|
} else {
|
|
// For a non-module create the GeneratedPluginRegistrant as source files
|
|
// directly in the ios project.
|
|
final String runnerDirectory = fs.path.join(directory, 'Runner');
|
|
_renderTemplateToFile(
|
|
_iosPluginRegistryHeaderTemplate,
|
|
context,
|
|
fs.path.join(runnerDirectory, 'GeneratedPluginRegistrant.h'),
|
|
);
|
|
_renderTemplateToFile(
|
|
_iosPluginRegistryImplementationTemplate,
|
|
context,
|
|
fs.path.join(runnerDirectory, 'GeneratedPluginRegistrant.m'),
|
|
);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/// Injects plugins found in `pubspec.yaml` into the platform-specific projects.
|
|
void injectPlugins({@required String projectPath, @required FlutterManifest manifest}) {
|
|
final List<Plugin> plugins = findPlugins(projectPath);
|
|
final bool changed = _writeFlutterPluginsList(projectPath, plugins);
|
|
if (manifest.isModule) {
|
|
_writeAndroidPluginRegistrant(fs.path.join(projectPath, '.android', 'Flutter'), plugins);
|
|
} else if (fs.isDirectorySync(fs.path.join(projectPath, 'android', 'app'))) {
|
|
_writeAndroidPluginRegistrant(fs.path.join(projectPath, 'android', 'app'), plugins);
|
|
}
|
|
if (manifest.isModule) {
|
|
_writeIOSPluginRegistrant(fs.path.join(projectPath, '.ios'), manifest, plugins);
|
|
} else if (fs.isDirectorySync(fs.path.join(projectPath, 'ios'))) {
|
|
_writeIOSPluginRegistrant(fs.path.join(projectPath, 'ios'), manifest, plugins);
|
|
final CocoaPods cocoaPods = new CocoaPods();
|
|
if (plugins.isNotEmpty)
|
|
cocoaPods.setupPodfile(projectPath, manifest);
|
|
if (changed)
|
|
cocoaPods.invalidatePodInstallOutput(projectPath);
|
|
}
|
|
}
|
|
|
|
/// 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;
|
|
}
|