Files
flutter-elinux/lib/elinux_plugins.dart
T
Hidenori Matsubayashi 070d039f2d Update for flutter 3.10.0 (#182)
Signed-off-by: Hidenori Matsubayashi <hidenori.matsubayashi@gmail.com>
2023-05-20 04:58:32 +09:00

683 lines
23 KiB
Dart

// Copyright 2023 Sony Group Corporation. All rights reserved.
// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved.
// 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 'dart:convert';
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/error_handling_io.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/build_system/targets/web.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/dart/language_version.dart';
import 'package:flutter_tools/src/dart/package_map.dart';
import 'package:flutter_tools/src/flutter_plugins.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/platform_plugins.dart';
import 'package:flutter_tools/src/plugins.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
import 'elinux_cmake_project.dart';
/// Source: [LinuxPlugin] in `platform_plugins.dart`
class ELinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
ELinuxPlugin({
required this.name,
required this.directory,
this.pluginClass,
this.dartPluginClass,
this.ffiPlugin,
this.defaultPackage,
this.dependencies,
}) : assert(pluginClass != null ||
dartPluginClass != null ||
(ffiPlugin ?? false) ||
defaultPackage != null);
factory ELinuxPlugin.fromYaml(String name, Directory directory, YamlMap yaml,
List<String> dependencies) {
assert(validate(yaml));
// Treat 'none' as not present. See https://github.com/flutter/flutter/issues/57497.
String? pluginClass = yaml[kPluginClass] as String;
if (pluginClass == 'none') {
pluginClass = null;
}
return ELinuxPlugin(
name: name,
directory: directory,
pluginClass: yaml[kPluginClass] as String?,
dartPluginClass: yaml[kDartPluginClass] as String?,
ffiPlugin: yaml[kFfiPlugin] as bool?,
defaultPackage: yaml[kDefaultPackage] as String?,
dependencies: dependencies);
}
static bool validate(YamlMap yaml) {
return yaml[kPluginClass] is String ||
yaml[kDartPluginClass] is String ||
yaml[kFfiPlugin] == true ||
yaml[kDefaultPackage] is String;
}
static const String kConfigKey = 'elinux';
final String name;
final Directory directory;
final String? pluginClass;
final String? dartPluginClass;
final List<String>? dependencies;
final bool? ffiPlugin;
final String? defaultPackage;
@override
bool hasMethodChannel() => pluginClass != null;
@override
bool hasFfi() => ffiPlugin != null;
@override
bool hasDart() => dartPluginClass != null;
@override
Map<String, dynamic> toMap() {
return <String, dynamic>{
'name': name,
if (pluginClass != null) 'class': pluginClass,
if (pluginClass != null) 'filename': _filenameForCppClass(pluginClass!),
if (dartPluginClass != null) 'dartPluginClass': dartPluginClass,
if (ffiPlugin != null && ffiPlugin!) kFfiPlugin: true,
if (defaultPackage != null) kDefaultPackage: defaultPackage,
};
}
String get path => directory.parent.path;
}
/// Source: [_internalCapitalLetterRegex] in `platform_plugins.dart` (exact copy)
final RegExp _internalCapitalLetterRegex = RegExp(r'(?=(?!^)[A-Z])');
String _filenameForCppClass(String className) {
return className.splitMapJoin(_internalCapitalLetterRegex,
onMatch: (_) => '_', onNonMatch: (String n) => n.toLowerCase());
}
/// See: [FlutterCommand.verifyThenRunCommand] in `flutter_command.dart`
mixin ELinuxExtension on FlutterCommand {
String? _entrypoint;
bool get _usesTargetOption => argParser.options.containsKey('target');
@override
Future<FlutterCommandResult> verifyThenRunCommand(String? commandPath) async {
if (super.shouldRunPub) {
// TODO(swift-kim): Should run pub get first before injecting plugins.
await ensureReadyForELinuxTooling(FlutterProject.current());
}
if (_usesTargetOption) {
_entrypoint =
await _createEntrypoint(FlutterProject.current(), super.targetFile);
}
return super.verifyThenRunCommand(commandPath);
}
@override
String get targetFile => _entrypoint ?? super.targetFile;
}
/// Creates an entrypoint wrapper of [targetFile] and returns its path.
/// This effectively adds support for Dart plugins.
///
/// Source: [WebEntrypointTarget.build] in `web.dart`
Future<String> _createEntrypoint(
FlutterProject project, String targetFile) async {
final List<ELinuxPlugin> dartPlugins =
await findELinuxPlugins(project, dartOnly: true);
if (dartPlugins.isEmpty) {
return targetFile;
}
final ELinuxProject eLinuxProject = ELinuxProject.fromFlutter(project);
if (!eLinuxProject.existsSync()) {
return targetFile;
}
final File entrypoint = eLinuxProject.managedDirectory.childFile('main.dart')
..createSync(recursive: true);
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
project.directory.childFile('.packages'),
logger: globals.logger,
);
final FlutterProject flutterProject = FlutterProject.current();
final LanguageVersion languageVersion = determineLanguageVersion(
globals.fs.file(targetFile),
packageConfig[flutterProject.manifest.appName],
Cache.flutterRoot!,
);
final Uri mainUri = globals.fs.file(targetFile).absolute.uri;
final String mainImport =
packageConfig.toPackageUri(mainUri)?.toString() ?? mainUri.toString();
entrypoint.writeAsStringSync('''
//
// Generated file. Do not edit.
//
// @dart=${languageVersion.major}.${languageVersion.minor}
import '$mainImport' as entrypoint;
import 'generated_plugin_registrant.dart';
Future<void> main() async {
registerPlugins();
entrypoint.main();
}
''');
return entrypoint.path;
}
const List<String> _knownPlugins = <String>[
'audioplayers',
'battery',
'battery_plus',
'camera',
'connectivity',
'device_info',
'flutter_tts',
'image_picker',
'integration_test',
'package_info',
'package_info_plus',
'path_provider',
'permission_handler',
'sensors',
'sensors_plus',
'share',
'shared_preferences',
'url_launcher',
'video_player',
'webview_flutter',
'wifi_info_flutter',
];
/// This function must be called whenever [FlutterProject.regeneratePlatformSpecificTooling]
/// or [FlutterProject.ensureReadyForPlatformSpecificTooling] is called.
///
/// See: [FlutterProject.ensureReadyForPlatformSpecificTooling] in `project.dart`
Future<void> ensureReadyForELinuxTooling(FlutterProject project) async {
if (!project.directory.existsSync() ||
project.hasExampleApp ||
project.isPlugin) {
return;
}
final ELinuxProject eLinuxProject = ELinuxProject.fromFlutter(project);
await eLinuxProject.ensureReadyForPlatformSpecificTooling();
await injectELinuxPlugins(project);
}
/// See: [refreshPluginsList] in `flutter_plugins.dart`
Future<void> refreshELinuxPluginsList(FlutterProject project) async {
final List<ELinuxPlugin> plugins = await findELinuxPlugins(project);
// Sort the plugins by name to keep ordering stable in generated files.
plugins.sort((ELinuxPlugin left, ELinuxPlugin right) =>
left.name.compareTo(right.name));
// TODO(franciscojma): Remove once migration is complete.
// Write the legacy plugin files to avoid breaking existing apps.
final bool legacyChanged =
_writeELinuxFlutterPluginsListLegacy(project, plugins);
final bool changed = await _writeELinuxFlutterPluginsList(project, plugins);
if (changed || legacyChanged) {
createPluginSymlinks(project, force: true);
}
}
/// See: [_writeFlutterPluginsListLegacy] in `flutter_plugins.dart`
bool _writeELinuxFlutterPluginsListLegacy(
FlutterProject project, List<ELinuxPlugin> plugins) {
final File pluginsFile = project.flutterPluginsFile;
if (plugins.isEmpty) {
return ErrorHandlingFileSystem.deleteIfExists(pluginsFile);
}
const String info =
'This is a generated file; do not edit or check into version control.';
final StringBuffer flutterPluginsBuffer = StringBuffer('# $info\n');
for (final ELinuxPlugin plugin in plugins) {
flutterPluginsBuffer
.write('${plugin.name}=${globals.fsUtils.escapePath(plugin.path)}\n');
}
final String? oldPluginFileContent = _readFileContent(pluginsFile);
final String pluginFileContent = flutterPluginsBuffer.toString();
pluginsFile.writeAsStringSync(pluginFileContent, flush: true);
return oldPluginFileContent != _readFileContent(pluginsFile);
}
// Key strings for the .flutter-plugins-dependencies file.
const String _kFlutterPluginsPluginListKey = 'plugins';
const String _kFlutterPluginsNameKey = 'name';
const String _kFlutterPluginsPathKey = 'path';
const String _kFlutterPluginsDependenciesKey = 'dependencies';
/// See: [_writeFlutterPluginsList] in `flutter_plugins.dart`
Future<bool> _writeELinuxFlutterPluginsList(
FlutterProject project, List<ELinuxPlugin> plugins) async {
final File pluginsFile = project.flutterPluginsDependenciesFile;
if (plugins.isEmpty) {
return ErrorHandlingFileSystem.deleteIfExists(pluginsFile);
}
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 String elinuxKey = ELinuxProject.fromFlutter(project).pluginConfigKey;
final Map<String, Object> pluginsMap = <String, Object>{};
{
final List<Plugin> plugins = await findPlugins(project);
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);
}
pluginsMap[elinuxKey] = _filterELinuxPluginsByPlatform(plugins, elinuxKey);
final Map<String, Object> result = <String, Object>{};
result['info'] =
'This is a generated file; do not edit or check into version control.';
result[_kFlutterPluginsPluginListKey] = 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'] = globals.systemClock.now().toString();
result['version'] = globals.flutterVersion.frameworkVersion;
// Only notify if the plugins list has changed. [date_created] will always be different,
// [version] is not relevant for this check.
const bool pluginsChanged = true;
//final String oldPluginsFileStringContent = _readFileContent(pluginsFile);
//if (oldPluginsFileStringContent != null) {
// pluginsChanged =
// oldPluginsFileStringContent.contains(pluginsMap.toString());
//}
final String pluginFileContent = json.encode(result);
pluginsFile.writeAsStringSync(pluginFileContent, flush: true);
return pluginsChanged;
}
/// See: [_filterPluginsByPlatform] in `plugins.dart` (exact copy)
List<Map<String, Object>> _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, Object>> pluginInfo = <Map<String, Object>>[];
for (final Plugin plugin in platformPlugins) {
pluginInfo.add(<String, Object>{
_kFlutterPluginsNameKey: plugin.name,
_kFlutterPluginsPathKey: globals.fsUtils.escapePath(plugin.path),
_kFlutterPluginsDependenciesKey: <String>[
...plugin.dependencies.where(pluginNames.contains)
],
});
}
return pluginInfo;
}
/// See: [_filterPluginsByPlatform] in `plugins.dart`
List<Map<String, Object>> _filterELinuxPluginsByPlatform(
List<ELinuxPlugin> plugins, String platformKey) {
final Set<String> pluginNames =
plugins.map((ELinuxPlugin plugin) => plugin.name).toSet();
final List<Map<String, Object>> pluginInfo = <Map<String, Object>>[];
for (final ELinuxPlugin plugin in plugins) {
pluginInfo.add(<String, Object>{
_kFlutterPluginsNameKey: plugin.name,
_kFlutterPluginsPathKey: globals.fsUtils.escapePath(plugin.path),
_kFlutterPluginsDependenciesKey: <String>[
...plugin.dependencies!.where(pluginNames.contains)
],
});
}
return pluginInfo;
}
/// See: [_createPluginLegacyDependencyGraph] in `flutter_plugins.dart`
List<Object> _createPluginLegacyDependencyGraph(List<ELinuxPlugin> plugins) {
final List<Object> directAppDependencies = <Object>[];
final Set<String> pluginNames =
plugins.map((ELinuxPlugin plugin) => plugin.name).toSet();
for (final ELinuxPlugin plugin in plugins) {
directAppDependencies.add(<String, Object>{
'name': plugin.name,
// Extract the plugin dependencies which happen to be plugins.
'dependencies': <String>[
...plugin.dependencies!.where(pluginNames.contains)
],
});
}
return directAppDependencies;
}
/// See: [injectPlugins] in `plugins.dart`
Future<void> injectELinuxPlugins(FlutterProject project) async {
final ELinuxProject eLinuxProject = ELinuxProject.fromFlutter(project);
if (eLinuxProject.existsSync()) {
final List<ELinuxPlugin> dartPlugins =
await findELinuxPlugins(project, dartOnly: true);
final List<ELinuxPlugin> nativePlugins =
await findELinuxPlugins(project, nativeOnly: true);
_writeDartPluginRegistrant(eLinuxProject.managedDirectory, dartPlugins);
_writePluginCmakefileTemplate(
eLinuxProject, eLinuxProject.managedDirectory, nativePlugins);
}
final List<String> plugins = (await findELinuxPlugins(project))
.map((ELinuxPlugin p) => p.name)
.toList();
for (final String plugin in plugins) {
final String eLinuxPlugin = '${plugin}_elinux';
if (_knownPlugins.contains(plugin) && !plugins.contains(eLinuxPlugin)) {
globals.printStatus(
'$eLinuxPlugin is available on pub.dev. Did you forget to add to pubspec.yaml?',
color: TerminalColor.yellow,
);
}
}
}
/// Source: [findPlugins] in `plugins.dart`
Future<List<ELinuxPlugin>> findELinuxPlugins(
FlutterProject project, {
bool dartOnly = false,
bool nativeOnly = false,
bool throwOnError = true,
}) async {
final List<ELinuxPlugin> plugins = <ELinuxPlugin>[];
final File packagesFile = project.directory.childFile('.packages');
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
packagesFile,
logger: globals.logger,
throwOnError: throwOnError,
);
for (final Package package in packageConfig.packages) {
final Uri packageRoot = package.packageUriRoot.resolve('..');
final ELinuxPlugin? plugin = _pluginFromPackage(package.name, packageRoot);
if (plugin == null) {
continue;
} else if (nativeOnly &&
(plugin.pluginClass == null || plugin.pluginClass == 'none')) {
continue;
} else if (dartOnly && plugin.dartPluginClass == null) {
continue;
}
plugins.add(plugin);
}
return plugins;
}
/// Source: [_pluginFromPackage] in `plugins.dart`
ELinuxPlugin? _pluginFromPackage(String name, Uri packageRoot) {
final String pubspecPath =
globals.fs.path.fromUri(packageRoot.resolve('pubspec.yaml'));
if (!globals.fs.isFileSync(pubspecPath)) {
return null;
}
dynamic pubspec;
try {
pubspec = loadYaml(globals.fs.file(pubspecPath).readAsStringSync());
} on YamlException catch (err) {
globals.printTrace('Failed to parse plugin manifest for $name: $err');
}
if (pubspec == null) {
return null;
}
final dynamic flutterConfig = pubspec['flutter'];
if (flutterConfig == null || !(flutterConfig.containsKey('plugin') as bool)) {
return null;
}
final Directory packageDir = globals.fs.directory(packageRoot);
globals.printTrace('Found plugin $name at ${packageDir.path}');
final YamlMap pluginYaml = flutterConfig['plugin'] as YamlMap;
if (pluginYaml['platforms'] == null) {
return null;
}
final YamlMap platformsYaml = pluginYaml['platforms'] as YamlMap;
if (platformsYaml[ELinuxPlugin.kConfigKey] == null) {
return null;
}
final YamlMap dependencies = pubspec['dependencies'] as YamlMap;
return ELinuxPlugin.fromYaml(
name,
packageDir.childDirectory('elinux'),
platformsYaml[ELinuxPlugin.kConfigKey] as YamlMap,
<String>[...dependencies.keys.cast<String>()],
);
}
/// See: [_writeWebPluginRegistrant] in `plugins.dart`
void _writeDartPluginRegistrant(
Directory registryDirectory,
List<ELinuxPlugin> plugins,
) {
final List<Map<String, dynamic>> pluginConfigs =
plugins.map((ELinuxPlugin plugin) => plugin.toMap()).toList();
final Map<String, dynamic> context = <String, dynamic>{
'plugins': pluginConfigs,
};
_renderTemplateToFile(
'''
//
// Generated file. Do not edit.
//
// ignore_for_file: lines_longer_than_80_chars
{{#plugins}}
import 'package:{{name}}/{{name}}.dart';
{{/plugins}}
// ignore: public_member_api_docs
void registerPlugins() {
{{#plugins}}
{{dartPluginClass}}.registerWith();
{{/plugins}}
}
''',
context,
registryDirectory.childFile('generated_plugin_registrant.dart').path,
);
}
/// See: [_writeWindowsPluginFiles] in `plugins.dart`
void _writePluginCmakefileTemplate(
ELinuxProject eLinuxProject,
Directory registryDirectory,
List<ELinuxPlugin> plugins,
) {
final List<Map<String, dynamic>> pluginConfigs =
plugins.map((ELinuxPlugin plugin) => plugin.toMap()).toList();
final Map<String, dynamic> context = <String, dynamic>{
'plugins': pluginConfigs,
'pluginsDir': _cmakeRelativePluginSymlinkDirectoryPath(eLinuxProject),
};
_renderTemplateToFile(
'''
//
// Generated file. Do not edit.
//
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
''',
context,
registryDirectory.childFile('generated_plugin_registrant.h').path,
);
_renderTemplateToFile(
'''
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
{{#plugins}}
#include <{{name}}/{{filename}}.h>
{{/plugins}}
void RegisterPlugins(flutter::PluginRegistry* registry) {
{{#plugins}}
{{class}}RegisterWithRegistrar(
registry->GetRegistrarForPlugin("{{class}}"));
{{/plugins}}
}
''',
context,
registryDirectory.childFile('generated_plugin_registrant.cc').path,
);
_renderTemplateToFile(
r'''
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
{{#plugins}}
{{name}}
{{/plugins}}
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory({{pluginsDir}}/${plugin}/elinux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
''',
context,
registryDirectory.childFile('generated_plugins.cmake').path,
);
}
/// Source: [_cmakeRelativePluginSymlinkDirectoryPath] in `flutter_plugins.dart`
String _cmakeRelativePluginSymlinkDirectoryPath(CmakeBasedProject project) {
final FileSystem fileSystem = project.pluginSymlinkDirectory.fileSystem;
final String makefileDirPath = project.cmakeFile.parent.absolute.path;
// CMake always uses posix-style path separators, regardless of the platform.
final path.Context cmakePathContext = path.Context(style: path.Style.posix);
final List<String> relativePathComponents =
fileSystem.path.split(fileSystem.path.relative(
project.pluginSymlinkDirectory.absolute.path,
from: makefileDirPath,
));
return cmakePathContext.joinAll(relativePathComponents);
}
/// Source: [_renderTemplateToFile] in `plugins.dart` (exact copy)
void _renderTemplateToFile(String template, dynamic context, String filePath) {
final String renderedTemplate = globals.templateRenderer
.renderString(template, context, htmlEscapeValues: false);
final File file = globals.fs.file(filePath);
file.createSync(recursive: true);
file.writeAsStringSync(renderedTemplate);
}
/// Source: [createPluginSymlinks] in `flutter_plugins.dart`
void createPluginSymlinks(FlutterProject project, {bool force = false}) {
Map<String, Object?>? platformPlugins;
final String? pluginFileContent =
_readFileContent(project.flutterPluginsDependenciesFile);
if (pluginFileContent != null) {
final Map<String, Object?>? pluginInfo =
json.decode(pluginFileContent) as Map<String, Object?>?;
platformPlugins =
pluginInfo?[_kFlutterPluginsPluginListKey] as Map<String, Object?>?;
}
platformPlugins ??= <String, Object?>{};
final ELinuxProject eLinuxProject = ELinuxProject.fromFlutter(project);
if (eLinuxProject.existsSync()) {
_createPlatformPluginSymlinks(
eLinuxProject.pluginSymlinkDirectory,
platformPlugins[eLinuxProject.pluginConfigKey] as List<Object?>?,
force: force,
);
}
}
/// Returns the contents of [File] or [null] if that file does not exist.
String? _readFileContent(File file) {
return file.existsSync() ? file.readAsStringSync() : null;
}
/// Creates [symlinkDirectory] containing symlinks to each plugin listed in [platformPlugins].
///
/// If [force] is true, the directory will be created only if missing.
void _createPlatformPluginSymlinks(
Directory symlinkDirectory, List<Object?>? platformPlugins,
{bool force = false}) {
if (force && symlinkDirectory.existsSync()) {
// Start fresh to avoid stale links.
symlinkDirectory.deleteSync(recursive: true);
}
symlinkDirectory.createSync(recursive: true);
if (platformPlugins == null) {
return;
}
for (final Map<String, Object?> pluginInfo
in platformPlugins.cast<Map<String, Object?>>()) {
final String name = pluginInfo[_kFlutterPluginsNameKey]! as String;
final String path = pluginInfo[_kFlutterPluginsPathKey]! as String;
final Link link = symlinkDirectory.childLink(name);
if (link.existsSync()) {
continue;
}
try {
link.createSync(path);
} on FileSystemException catch (e) {
// ignore: invalid_use_of_visible_for_testing_member
handleSymlinkException(e,
platform: globals.platform,
os: globals.os,
destination: 'dest',
source: 'source');
rethrow;
}
}
}