feat: Support overriding native endorsed plugins (#137040)
These changes allow to override existing native endorsed (federated & inline) plugins with e.g. non-endorsed plugin implementations via direct dependencies as described in the section *Platform implementation selection* in the [design doc](https://docs.google.com/document/d/1LD7QjmzJZLCopUrFAAE98wOUQpjmguyGTN2wd_89Srs). ```yaml # pubspec.yaml name: example dependencies: my_plugin: ^0.0.1 my_plugin_android_alternative: ^0.0.1 ``` Closes #80374, closes #59657 Related: Override Dart plugins (#87991, #79669)
This commit is contained in:
parent
90025e2964
commit
d55a5f96c1
@ -106,6 +106,12 @@ Future<List<Plugin>> findPlugins(FlutterProject project, { bool throwOnError = t
|
||||
return plugins;
|
||||
}
|
||||
|
||||
/// Plugin resolution type to determine the injection mechanism.
|
||||
enum _PluginResolutionType {
|
||||
dart,
|
||||
nativeOrDart,
|
||||
}
|
||||
|
||||
// Key strings for the .flutter-plugins-dependencies file.
|
||||
const String _kFlutterPluginsPluginListKey = 'plugins';
|
||||
const String _kFlutterPluginsNameKey = 'name';
|
||||
@ -181,9 +187,14 @@ bool _writeFlutterPluginsList(
|
||||
project.web.pluginConfigKey,
|
||||
];
|
||||
|
||||
final Map<String, List<Plugin>> resolvedPlatformPlugins = _resolvePluginImplementations(
|
||||
plugins,
|
||||
pluginResolutionType: _PluginResolutionType.nativeOrDart,
|
||||
);
|
||||
|
||||
final Map<String, Object> pluginsMap = <String, Object>{};
|
||||
for (final String platformKey in platformKeys) {
|
||||
pluginsMap[platformKey] = _createPluginMapOfPlatform(plugins, platformKey);
|
||||
pluginsMap[platformKey] = _createPluginMapOfPlatform(resolvedPlatformPlugins[platformKey] ?? <Plugin>[], platformKey);
|
||||
}
|
||||
|
||||
final Map<String, Object> result = <String, Object>{};
|
||||
@ -212,18 +223,15 @@ bool _writeFlutterPluginsList(
|
||||
}
|
||||
|
||||
/// Creates a map representation of the [plugins] for those supported by [platformKey].
|
||||
/// All given [plugins] must provide an implementation for the [platformKey].
|
||||
List<Map<String, Object>> _createPluginMapOfPlatform(
|
||||
List<Plugin> plugins,
|
||||
String platformKey,
|
||||
) {
|
||||
final Iterable<Plugin> resolvedPlatformPlugins = plugins.where((Plugin p) {
|
||||
return p.platforms.containsKey(platformKey);
|
||||
});
|
||||
|
||||
final Set<String> pluginNames = resolvedPlatformPlugins.map((Plugin plugin) => plugin.name).toSet();
|
||||
final Set<String> pluginNames = plugins.map((Plugin plugin) => plugin.name).toSet();
|
||||
final List<Map<String, Object>> pluginInfo = <Map<String, Object>>[];
|
||||
for (final Plugin plugin in resolvedPlatformPlugins) {
|
||||
// This is guaranteed to be non-null due to the `where` filter above.
|
||||
for (final Plugin plugin in plugins) {
|
||||
assert(plugin.platforms[platformKey] != null, 'Plugin ${plugin.name} does not provide an implementation for $platformKey.');
|
||||
final PluginPlatform platformPlugin = plugin.platforms[platformKey]!;
|
||||
pluginInfo.add(<String, Object>{
|
||||
_kFlutterPluginsNameKey: plugin.name,
|
||||
@ -1051,10 +1059,9 @@ Future<void> injectBuildTimePluginFiles(
|
||||
bool webPlatform = false,
|
||||
}) async {
|
||||
final List<Plugin> plugins = await findPlugins(project);
|
||||
// Sort the plugins by name to keep ordering stable in generated files.
|
||||
plugins.sort((Plugin left, Plugin right) => left.name.compareTo(right.name));
|
||||
final Map<String, List<Plugin>> pluginsByPlatform = _resolvePluginImplementations(plugins, pluginResolutionType: _PluginResolutionType.nativeOrDart);
|
||||
if (webPlatform) {
|
||||
await _writeWebPluginRegistrant(project, plugins, destination);
|
||||
await _writeWebPluginRegistrant(project, pluginsByPlatform[WebPlugin.kConfigKey]!, destination);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1081,22 +1088,22 @@ Future<void> injectPlugins(
|
||||
DarwinDependencyManagement? darwinDependencyManagement,
|
||||
}) async {
|
||||
final List<Plugin> plugins = await findPlugins(project);
|
||||
// Sort the plugins by name to keep ordering stable in generated files.
|
||||
plugins.sort((Plugin left, Plugin right) => left.name.compareTo(right.name));
|
||||
final Map<String, List<Plugin>> pluginsByPlatform = _resolvePluginImplementations(plugins, pluginResolutionType: _PluginResolutionType.nativeOrDart);
|
||||
|
||||
if (androidPlatform) {
|
||||
await _writeAndroidPluginRegistrant(project, plugins);
|
||||
await _writeAndroidPluginRegistrant(project, pluginsByPlatform[AndroidPlugin.kConfigKey]!);
|
||||
}
|
||||
if (iosPlatform) {
|
||||
await _writeIOSPluginRegistrant(project, plugins);
|
||||
await _writeIOSPluginRegistrant(project, pluginsByPlatform[IOSPlugin.kConfigKey]!);
|
||||
}
|
||||
if (linuxPlatform) {
|
||||
await _writeLinuxPluginFiles(project, plugins);
|
||||
await _writeLinuxPluginFiles(project, pluginsByPlatform[LinuxPlugin.kConfigKey]!);
|
||||
}
|
||||
if (macOSPlatform) {
|
||||
await _writeMacOSPluginRegistrant(project, plugins);
|
||||
await _writeMacOSPluginRegistrant(project, pluginsByPlatform[MacOSPlugin.kConfigKey]!);
|
||||
}
|
||||
if (windowsPlatform) {
|
||||
await writeWindowsPluginFiles(project, plugins, globals.templateRenderer, allowedPlugins: allowedPlugins);
|
||||
await writeWindowsPluginFiles(project, pluginsByPlatform[WindowsPlugin.kConfigKey]!, globals.templateRenderer, allowedPlugins: allowedPlugins);
|
||||
}
|
||||
if (iosPlatform || macOSPlatform) {
|
||||
final DarwinDependencyManagement darwinDependencyManagerSetup = darwinDependencyManagement ?? DarwinDependencyManagement(
|
||||
@ -1130,7 +1137,7 @@ bool hasPlugins(FlutterProject project) {
|
||||
return _readFileContent(project.flutterPluginsFile) != null;
|
||||
}
|
||||
|
||||
/// Resolves the platform implementation for Dart-only plugins.
|
||||
/// Resolves the plugin implementations for all platforms.
|
||||
///
|
||||
/// * If there is only one dependency on a package that implements the
|
||||
/// frontend plugin for the current platform, use that.
|
||||
@ -1142,73 +1149,62 @@ bool hasPlugins(FlutterProject project) {
|
||||
/// * Else fail.
|
||||
///
|
||||
/// For more details, https://flutter.dev/go/federated-plugins.
|
||||
// TODO(stuartmorgan): Expand implementation to apply to all implementations,
|
||||
// not just Dart-only, per the federated plugin spec.
|
||||
///
|
||||
/// If [selectDartPluginsOnly] is enabled, only Dart plugin implementations are
|
||||
/// considered. Else, native and Dart plugin implementations are considered.
|
||||
List<PluginInterfaceResolution> resolvePlatformImplementation(
|
||||
List<Plugin> plugins,
|
||||
) {
|
||||
const Iterable<String> platformKeys = <String>[
|
||||
AndroidPlugin.kConfigKey,
|
||||
IOSPlugin.kConfigKey,
|
||||
LinuxPlugin.kConfigKey,
|
||||
MacOSPlugin.kConfigKey,
|
||||
WindowsPlugin.kConfigKey,
|
||||
];
|
||||
final List<PluginInterfaceResolution> pluginResolutions = <PluginInterfaceResolution>[];
|
||||
bool hasResolutionError = false;
|
||||
List<Plugin> plugins, {
|
||||
required bool selectDartPluginsOnly,
|
||||
}) {
|
||||
final Map<String, List<Plugin>> resolution = _resolvePluginImplementations(
|
||||
plugins,
|
||||
pluginResolutionType: selectDartPluginsOnly ? _PluginResolutionType.dart : _PluginResolutionType.nativeOrDart,
|
||||
);
|
||||
return resolution.entries.expand((MapEntry<String, List<Plugin>> entry) {
|
||||
return entry.value.map((Plugin plugin) {
|
||||
return PluginInterfaceResolution(plugin: plugin, platform: entry.key);
|
||||
});
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// Resolves the plugin implementations for all platforms,
|
||||
/// see [resolvePlatformImplementation].
|
||||
///
|
||||
/// Only plugins which provide the according platform implementation are returned.
|
||||
Map<String, List<Plugin>> _resolvePluginImplementations(
|
||||
List<Plugin> plugins, {
|
||||
required _PluginResolutionType pluginResolutionType,
|
||||
}) {
|
||||
final Map<String, List<Plugin>> pluginsByPlatform = <String, List<Plugin>>{
|
||||
AndroidPlugin.kConfigKey: <Plugin>[],
|
||||
IOSPlugin.kConfigKey: <Plugin>[],
|
||||
LinuxPlugin.kConfigKey: <Plugin>[],
|
||||
MacOSPlugin.kConfigKey: <Plugin>[],
|
||||
WindowsPlugin.kConfigKey: <Plugin>[],
|
||||
WebPlugin.kConfigKey: <Plugin>[],
|
||||
};
|
||||
|
||||
bool hasPluginPubspecError = false;
|
||||
bool hasResolutionError = false;
|
||||
|
||||
for (final String platformKey in platformKeys) {
|
||||
// Key: the plugin name, value: the list of plugin candidates for the implementation of [platformKey].
|
||||
final Map<String, List<Plugin>> pluginImplCandidates = <String, List<Plugin>>{};
|
||||
|
||||
// Key: the plugin name, value: the plugin name of the default implementation of [platformKey].
|
||||
final Map<String, String> defaultImplementations = <String, String>{};
|
||||
|
||||
for (final Plugin plugin in plugins) {
|
||||
final String? error = _validatePlugin(plugin, platformKey);
|
||||
if (error != null) {
|
||||
globals.printError(error);
|
||||
hasPluginPubspecError = true;
|
||||
continue;
|
||||
}
|
||||
final String? implementsPluginName = _getImplementedPlugin(plugin, platformKey);
|
||||
final String? defaultImplPluginName = _getDefaultImplPlugin(plugin, platformKey);
|
||||
|
||||
if (defaultImplPluginName != null) {
|
||||
// Each plugin can only have one default implementation for this [platformKey].
|
||||
defaultImplementations[plugin.name] = defaultImplPluginName;
|
||||
}
|
||||
if (implementsPluginName != null) {
|
||||
pluginImplCandidates.putIfAbsent(implementsPluginName, () => <Plugin>[]);
|
||||
pluginImplCandidates[implementsPluginName]!.add(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
final Map<String, Plugin> pluginResolution = <String, Plugin>{};
|
||||
|
||||
// Now resolve all the possible resolutions to a single option for each
|
||||
// plugin, or throw if that's not possible.
|
||||
for (final MapEntry<String, List<Plugin>> implCandidatesEntry in pluginImplCandidates.entries) {
|
||||
final (Plugin? resolution, String? error) = _resolveImplementationOfPlugin(
|
||||
platformKey: platformKey,
|
||||
pluginName: implCandidatesEntry.key,
|
||||
candidates: implCandidatesEntry.value,
|
||||
defaultPackageName: defaultImplementations[implCandidatesEntry.key],
|
||||
);
|
||||
if (error != null) {
|
||||
globals.printError(error);
|
||||
hasResolutionError = true;
|
||||
} else if (resolution != null) {
|
||||
pluginResolution[implCandidatesEntry.key] = resolution;
|
||||
}
|
||||
}
|
||||
|
||||
pluginResolutions.addAll(
|
||||
pluginResolution.values.map((Plugin plugin) {
|
||||
return PluginInterfaceResolution(plugin: plugin, platform: platformKey);
|
||||
}),
|
||||
for (final String platformKey in pluginsByPlatform.keys) {
|
||||
final (
|
||||
List<Plugin> platformPluginResolutions,
|
||||
bool hasPlatformPluginPubspecError,
|
||||
bool hasPlatformResolutionError
|
||||
) = _resolvePluginImplementationsByPlatform(
|
||||
plugins,
|
||||
platformKey,
|
||||
pluginResolutionType: pluginResolutionType,
|
||||
);
|
||||
|
||||
if (hasPlatformPluginPubspecError) {
|
||||
hasPluginPubspecError = true;
|
||||
} else if (hasPlatformResolutionError) {
|
||||
hasResolutionError = true;
|
||||
} else {
|
||||
pluginsByPlatform[platformKey] = platformPluginResolutions;
|
||||
}
|
||||
}
|
||||
if (hasPluginPubspecError) {
|
||||
throwToolExit('Please resolve the plugin pubspec errors');
|
||||
@ -1216,14 +1212,103 @@ List<PluginInterfaceResolution> resolvePlatformImplementation(
|
||||
if (hasResolutionError) {
|
||||
throwToolExit('Please resolve the plugin implementation selection errors');
|
||||
}
|
||||
return pluginResolutions;
|
||||
return pluginsByPlatform;
|
||||
}
|
||||
|
||||
/// Resolves the plugins for the given [platformKey] (Dart-only or native
|
||||
/// implementations).
|
||||
(List<Plugin> pluginImplementations, bool hasPluginPubspecError, bool hasResolutionError) _resolvePluginImplementationsByPlatform(
|
||||
Iterable<Plugin> plugins,
|
||||
String platformKey, {
|
||||
_PluginResolutionType pluginResolutionType = _PluginResolutionType.nativeOrDart,
|
||||
}) {
|
||||
bool hasPluginPubspecError = false;
|
||||
bool hasResolutionError = false;
|
||||
|
||||
// Key: the plugin name, value: the list of plugin candidates for the implementation of [platformKey].
|
||||
final Map<String, List<Plugin>> pluginImplCandidates = <String, List<Plugin>>{};
|
||||
|
||||
// Key: the plugin name, value: the plugin of the default implementation of [platformKey].
|
||||
final Map<String, Plugin> defaultImplementations = <String, Plugin>{};
|
||||
|
||||
for (final Plugin plugin in plugins) {
|
||||
final String? error = _validatePlugin(plugin, platformKey, pluginResolutionType: pluginResolutionType);
|
||||
if (error != null) {
|
||||
globals.printError(error);
|
||||
hasPluginPubspecError = true;
|
||||
continue;
|
||||
}
|
||||
final String? implementsPluginName = _getImplementedPlugin(plugin, platformKey, pluginResolutionType: pluginResolutionType);
|
||||
final String? defaultImplPluginName = _getDefaultImplPlugin(plugin, platformKey, pluginResolutionType: pluginResolutionType);
|
||||
|
||||
if (defaultImplPluginName != null) {
|
||||
final Plugin? defaultPackage = plugins.where((Plugin plugin) => plugin.name == defaultImplPluginName).firstOrNull;
|
||||
if (defaultPackage != null) {
|
||||
if (_hasPluginInlineImpl(defaultPackage, platformKey, pluginResolutionType: _PluginResolutionType.nativeOrDart)) {
|
||||
if (pluginResolutionType == _PluginResolutionType.nativeOrDart ||
|
||||
_hasPluginInlineImpl(defaultPackage, platformKey, pluginResolutionType: pluginResolutionType)) {
|
||||
// Each plugin can only have one default implementation for this [platformKey].
|
||||
defaultImplementations[plugin.name] = defaultPackage;
|
||||
// No need to add the default plugin to `pluginImplCandidates`,
|
||||
// as if the plugin is present and provides an implementation
|
||||
// it is added via `_getImplementedPlugin`.
|
||||
}
|
||||
} else {
|
||||
// Only warn, if neither an implementation for native nor for Dart is given.
|
||||
globals.printWarning(
|
||||
'Package ${plugin.name}:$platformKey references $defaultImplPluginName:$platformKey as the default plugin, but it does not provide an inline implementation.\n'
|
||||
'Ask the maintainers of ${plugin.name} to either avoid referencing a default implementation via `platforms: $platformKey: default_package: $defaultImplPluginName` '
|
||||
'or add an inline implementation to $defaultImplPluginName via `platforms: $platformKey:` `pluginClass` or `dartPluginClass`.\n',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
globals.printWarning(
|
||||
'Package ${plugin.name}:$platformKey references $defaultImplPluginName:$platformKey as the default plugin, but the package does not exist.\n'
|
||||
'Ask the maintainers of ${plugin.name} to either avoid referencing a default implementation via `platforms: $platformKey: default_package: $defaultImplPluginName` '
|
||||
'or create a plugin named $defaultImplPluginName.\n',
|
||||
);
|
||||
}
|
||||
}
|
||||
if (implementsPluginName != null) {
|
||||
pluginImplCandidates.putIfAbsent(implementsPluginName, () => <Plugin>[]);
|
||||
pluginImplCandidates[implementsPluginName]!.add(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
// Key: the plugin name, value: the plugin which provides an implementation for [platformKey].
|
||||
final Map<String, Plugin> pluginResolution = <String, Plugin>{};
|
||||
|
||||
// Now resolve all the possible resolutions to a single option for each
|
||||
// plugin, or throw if that's not possible.
|
||||
for (final MapEntry<String, List<Plugin>> implCandidatesEntry in pluginImplCandidates.entries) {
|
||||
final (Plugin? resolution, String? error) = _resolveImplementationOfPlugin(
|
||||
platformKey: platformKey,
|
||||
pluginResolutionType: pluginResolutionType,
|
||||
pluginName: implCandidatesEntry.key,
|
||||
candidates: implCandidatesEntry.value,
|
||||
defaultPackage: defaultImplementations[implCandidatesEntry.key],
|
||||
);
|
||||
if (error != null) {
|
||||
globals.printError(error);
|
||||
hasResolutionError = true;
|
||||
} else if (resolution != null) {
|
||||
pluginResolution[implCandidatesEntry.key] = resolution;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the plugins by name to keep ordering stable in generated files.
|
||||
final List<Plugin> pluginImplementations = pluginResolution.values.toList()
|
||||
..sort((Plugin left, Plugin right) => left.name.compareTo(right.name));
|
||||
return (pluginImplementations, hasPluginPubspecError, hasResolutionError);
|
||||
}
|
||||
|
||||
/// Validates conflicting plugin parameters in pubspec, such as
|
||||
/// `dartPluginClass`, `default_package` and `implements`.
|
||||
///
|
||||
/// Returns an error, if failing.
|
||||
String? _validatePlugin(Plugin plugin, String platformKey) {
|
||||
String? _validatePlugin(Plugin plugin, String platformKey, {
|
||||
required _PluginResolutionType pluginResolutionType,
|
||||
}) {
|
||||
final String? implementsPackage = plugin.implementsPackage;
|
||||
final String? defaultImplPluginName = plugin.defaultPackagePlatforms[platformKey];
|
||||
|
||||
@ -1241,10 +1326,10 @@ String? _validatePlugin(Plugin plugin, String platformKey) {
|
||||
'or avoid referencing a default implementation via `platforms: $platformKey: default_package: $defaultImplPluginName`.\n';
|
||||
}
|
||||
|
||||
if (_hasPluginInlineDartImpl(plugin, platformKey)) {
|
||||
if (_hasPluginInlineImpl(plugin, platformKey, pluginResolutionType: pluginResolutionType)) {
|
||||
return 'Plugin ${plugin.name}:$platformKey which provides an inline implementation '
|
||||
'cannot also reference a default implementation for $defaultImplPluginName. '
|
||||
'Ask the maintainers of ${plugin.name} to either remove the implementation via `platforms: $platformKey: dartPluginClass` '
|
||||
'Ask the maintainers of ${plugin.name} to either remove the implementation via `platforms: $platformKey:${pluginResolutionType == _PluginResolutionType.dart ? ' dartPluginClass' : '` `pluginClass` or `dartPLuginClass'}` '
|
||||
'or avoid referencing a default implementation via `platforms: $platformKey: default_package: $defaultImplPluginName`.\n';
|
||||
}
|
||||
}
|
||||
@ -1262,19 +1347,22 @@ String? _validatePlugin(Plugin plugin, String platformKey) {
|
||||
/// * The [plugin] (e.g. 'url_launcher') implements itself and then also
|
||||
/// serves as its own default implementation.
|
||||
/// * The [plugin] does not provide an implementation.
|
||||
String? _getImplementedPlugin(Plugin plugin, String platformKey) {
|
||||
final bool hasInlineDartImpl = _hasPluginInlineDartImpl(plugin, platformKey);
|
||||
String? _getImplementedPlugin(
|
||||
Plugin plugin,
|
||||
String platformKey, {
|
||||
_PluginResolutionType pluginResolutionType = _PluginResolutionType.nativeOrDart,
|
||||
}) {
|
||||
if (_hasPluginInlineImpl(plugin, platformKey, pluginResolutionType: pluginResolutionType)) {
|
||||
// Only can serve, if the plugin has an inline implementation.
|
||||
|
||||
if (hasInlineDartImpl) {
|
||||
final String? implementsPackage = plugin.implementsPackage;
|
||||
|
||||
// Only can serve, if the plugin has a dart inline implementation.
|
||||
if (implementsPackage != null && implementsPackage.isNotEmpty) {
|
||||
// The inline plugin implements another package.
|
||||
return implementsPackage;
|
||||
}
|
||||
|
||||
if (_isEligibleDartSelfImpl(plugin, platformKey)) {
|
||||
// The inline Dart plugin implements itself.
|
||||
if (pluginResolutionType == _PluginResolutionType.nativeOrDart || _isEligibleDartSelfImpl(plugin, platformKey)) {
|
||||
// The inline plugin implements itself.
|
||||
return plugin.name;
|
||||
}
|
||||
}
|
||||
@ -1293,16 +1381,20 @@ String? _getImplementedPlugin(Plugin plugin, String platformKey) {
|
||||
/// * The [plugin] (e.g. 'url_launcher') implements itself and then also
|
||||
/// serves as its own default implementation.
|
||||
/// * The [plugin] does not reference a default implementation.
|
||||
String? _getDefaultImplPlugin(Plugin plugin, String platformKey) {
|
||||
String? _getDefaultImplPlugin(
|
||||
Plugin plugin,
|
||||
String platformKey, {
|
||||
_PluginResolutionType pluginResolutionType = _PluginResolutionType.nativeOrDart,
|
||||
}) {
|
||||
final String? defaultImplPluginName =
|
||||
plugin.defaultPackagePlatforms[platformKey];
|
||||
if (defaultImplPluginName != null) {
|
||||
return defaultImplPluginName;
|
||||
}
|
||||
|
||||
if (_hasPluginInlineDartImpl(plugin, platformKey) &&
|
||||
_isEligibleDartSelfImpl(plugin, platformKey)) {
|
||||
// The inline Dart plugin serves as its own default implementation.
|
||||
if (_hasPluginInlineImpl(plugin, platformKey, pluginResolutionType: pluginResolutionType) &&
|
||||
(pluginResolutionType == _PluginResolutionType.nativeOrDart || _isEligibleDartSelfImpl(plugin, platformKey))) {
|
||||
// The inline plugin serves as its own default implementation.
|
||||
return plugin.name;
|
||||
}
|
||||
|
||||
@ -1331,7 +1423,17 @@ bool _isEligibleDartSelfImpl(Plugin plugin, String platformKey) {
|
||||
return !isDesktop || hasMinVersionForImplementsRequirement;
|
||||
}
|
||||
|
||||
/// Determine if the plugin provides an inline dart implementation.
|
||||
/// Determine if the plugin provides an inline implementation.
|
||||
bool _hasPluginInlineImpl(
|
||||
Plugin plugin,
|
||||
String platformKey, {
|
||||
required _PluginResolutionType pluginResolutionType,
|
||||
}) {
|
||||
return pluginResolutionType == _PluginResolutionType.nativeOrDart && plugin.platforms[platformKey] != null ||
|
||||
pluginResolutionType == _PluginResolutionType.dart && _hasPluginInlineDartImpl(plugin, platformKey);
|
||||
}
|
||||
|
||||
/// Determine if the plugin provides an inline Dart implementation.
|
||||
bool _hasPluginInlineDartImpl(Plugin plugin, String platformKey) {
|
||||
return plugin.pluginDartClassPlatforms[platformKey] != null &&
|
||||
plugin.pluginDartClassPlatforms[platformKey] != 'none';
|
||||
@ -1343,9 +1445,10 @@ bool _hasPluginInlineDartImpl(Plugin plugin, String platformKey) {
|
||||
/// Returns an [error] string, if failing.
|
||||
(Plugin? resolution, String? error) _resolveImplementationOfPlugin({
|
||||
required String platformKey,
|
||||
required _PluginResolutionType pluginResolutionType,
|
||||
required String pluginName,
|
||||
required List<Plugin> candidates,
|
||||
String? defaultPackageName,
|
||||
Plugin? defaultPackage,
|
||||
}) {
|
||||
// If there's only one candidate, use it.
|
||||
if (candidates.length == 1) {
|
||||
@ -1377,12 +1480,10 @@ bool _hasPluginInlineDartImpl(Plugin plugin, String platformKey) {
|
||||
}
|
||||
}
|
||||
// Next, defer to the default implementation if there is one.
|
||||
if (defaultPackageName != null) {
|
||||
final int defaultIndex = candidates
|
||||
.indexWhere((Plugin plugin) => plugin.name == defaultPackageName);
|
||||
if (defaultIndex != -1) {
|
||||
return (candidates[defaultIndex], null);
|
||||
}
|
||||
if (defaultPackage != null && candidates.contains(defaultPackage)) {
|
||||
// By definition every candidate has an inline implementation
|
||||
assert(_hasPluginInlineImpl(defaultPackage, platformKey, pluginResolutionType: pluginResolutionType));
|
||||
return (defaultPackage, null);
|
||||
}
|
||||
// Otherwise, require an explicit choice.
|
||||
if (candidates.length > 1) {
|
||||
@ -1417,6 +1518,7 @@ Future<void> generateMainDartWithPluginRegistrant(
|
||||
final List<Plugin> plugins = await findPlugins(rootProject);
|
||||
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
|
||||
plugins,
|
||||
selectDartPluginsOnly: true,
|
||||
);
|
||||
final LanguageVersion entrypointVersion = determineLanguageVersion(
|
||||
mainFile,
|
||||
|
@ -142,8 +142,8 @@ class Plugin {
|
||||
}
|
||||
|
||||
// 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.
|
||||
// fact that its implementation of Dart-only plugins and default packages
|
||||
// are separate is legacy.
|
||||
final List<String> sharedHandlingPlatforms = <String>[
|
||||
AndroidPlugin.kConfigKey,
|
||||
IOSPlugin.kConfigKey,
|
||||
@ -382,7 +382,7 @@ class Plugin {
|
||||
/// 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.
|
||||
/// This is a mapping from platform config key to the Dart plugin class for the given platform.
|
||||
final Map<String, String> pluginDartClassPlatforms;
|
||||
|
||||
/// Whether this plugin is a direct dependency of the app.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@ import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:file_testing/file_testing.dart';
|
||||
import 'package:flutter_tools/src/base/error_handling_io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/os.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/base/time.dart';
|
||||
@ -892,11 +893,12 @@ dependencies:
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testUsingContext("Registrant for web doesn't escape slashes in imports", () async {
|
||||
flutterProject.isModule = true;
|
||||
final Directory webPluginWithNestedFile =
|
||||
fs.systemTempDirectory.createTempSync('flutter_web_plugin_with_nested.');
|
||||
webPluginWithNestedFile.childFile('pubspec.yaml').writeAsStringSync('''
|
||||
group('Build time plugin injection', () {
|
||||
testUsingContext("Registrant for web doesn't escape slashes in imports", () async {
|
||||
flutterProject.isModule = true;
|
||||
final Directory webPluginWithNestedFile =
|
||||
fs.systemTempDirectory.createTempSync('flutter_web_plugin_with_nested.');
|
||||
webPluginWithNestedFile.childFile('pubspec.yaml').writeAsStringSync('''
|
||||
flutter:
|
||||
plugin:
|
||||
platforms:
|
||||
@ -904,30 +906,85 @@ dependencies:
|
||||
pluginClass: WebPlugin
|
||||
fileName: src/web_plugin.dart
|
||||
''');
|
||||
webPluginWithNestedFile
|
||||
.childDirectory('lib')
|
||||
.childDirectory('src')
|
||||
.childFile('web_plugin.dart')
|
||||
.createSync(recursive: true);
|
||||
webPluginWithNestedFile
|
||||
.childDirectory('lib')
|
||||
.childDirectory('src')
|
||||
.childFile('web_plugin.dart')
|
||||
.createSync(recursive: true);
|
||||
|
||||
flutterProject.directory
|
||||
.childFile('.packages')
|
||||
.writeAsStringSync('''
|
||||
flutterProject.directory
|
||||
.childFile('.packages')
|
||||
.writeAsStringSync('''
|
||||
web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri}
|
||||
''');
|
||||
|
||||
final Directory destination = flutterProject.directory.childDirectory('lib');
|
||||
await injectBuildTimePluginFiles(flutterProject, webPlatform: true, destination: destination);
|
||||
final Directory destination = flutterProject.directory.childDirectory('lib');
|
||||
await injectBuildTimePluginFiles(flutterProject, webPlatform: true, destination: destination);
|
||||
|
||||
final File registrant = flutterProject.directory
|
||||
.childDirectory('lib')
|
||||
.childFile('web_plugin_registrant.dart');
|
||||
final File registrant = flutterProject.directory
|
||||
.childDirectory('lib')
|
||||
.childFile('web_plugin_registrant.dart');
|
||||
|
||||
expect(registrant.existsSync(), isTrue);
|
||||
expect(registrant.readAsStringSync(), contains("import 'package:web_plugin_with_nested/src/web_plugin.dart';"));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
expect(registrant.existsSync(), isTrue);
|
||||
expect(registrant.readAsStringSync(), contains("import 'package:web_plugin_with_nested/src/web_plugin.dart';"));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testUsingContext('user-selected implementation overrides inline implementation on web', () async {
|
||||
final List<Directory> directories = createFakePlugins(fs, <String>[
|
||||
'user_selected_url_launcher_implementation',
|
||||
'url_launcher',
|
||||
]);
|
||||
|
||||
// Add inline web implementation to `user_selected_url_launcher_implementation`
|
||||
directories[0].childFile('pubspec.yaml').writeAsStringSync('''
|
||||
flutter:
|
||||
plugin:
|
||||
implements: url_launcher
|
||||
platforms:
|
||||
web:
|
||||
pluginClass: UserSelectedUrlLauncherWeb
|
||||
fileName: src/web_plugin.dart
|
||||
''');
|
||||
|
||||
// Add inline native implementation to `url_launcher`
|
||||
directories[1].childFile('pubspec.yaml').writeAsStringSync('''
|
||||
flutter:
|
||||
plugin:
|
||||
platforms:
|
||||
web:
|
||||
pluginClass: InlineUrlLauncherWeb
|
||||
fileName: src/web_plugin.dart
|
||||
''');
|
||||
|
||||
final FlutterManifest manifest = FlutterManifest.createFromString('''
|
||||
name: test
|
||||
version: 1.0.0
|
||||
|
||||
dependencies:
|
||||
url_launcher: ^1.0.0
|
||||
user_selected_url_launcher_implementation: ^1.0.0
|
||||
''', logger: BufferLogger.test())!;
|
||||
|
||||
flutterProject.manifest = manifest;
|
||||
flutterProject.isModule = true;
|
||||
|
||||
final Directory destination = flutterProject.directory.childDirectory('lib');
|
||||
await injectBuildTimePluginFiles(flutterProject, webPlatform: true, destination: destination);
|
||||
|
||||
final File registrant = flutterProject.directory
|
||||
.childDirectory('lib')
|
||||
.childFile('web_plugin_registrant.dart');
|
||||
|
||||
expect(registrant.existsSync(), isTrue);
|
||||
expect(registrant.readAsStringSync(), contains("import 'package:user_selected_url_launcher_implementation/src/web_plugin.dart';"));
|
||||
expect(registrant.readAsStringSync(), isNot(contains("import 'package:url_launcher/src/web_plugin.dart';")));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
});
|
||||
|
||||
testUsingContext('Injecting creates generated Android registrant, but does not include Dart-only plugins', () async {
|
||||
@ -1075,6 +1132,112 @@ flutter:
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testUsingContext('user-selected implementation overrides inline implementation', () async {
|
||||
final List<Directory> directories = createFakePlugins(fs, <String>[
|
||||
'user_selected_url_launcher_implementation',
|
||||
'url_launcher',
|
||||
]);
|
||||
|
||||
// Add inline native implementation to `user_selected_url_launcher_implementation`
|
||||
directories[0].childFile('pubspec.yaml').writeAsStringSync('''
|
||||
flutter:
|
||||
plugin:
|
||||
implements: url_launcher
|
||||
platforms:
|
||||
linux:
|
||||
pluginClass: UserSelectedUrlLauncherLinux
|
||||
''');
|
||||
|
||||
// Add inline native implementation to `url_launcher`
|
||||
directories[1].childFile('pubspec.yaml').writeAsStringSync('''
|
||||
flutter:
|
||||
plugin:
|
||||
platforms:
|
||||
linux:
|
||||
pluginClass: InlineUrlLauncherLinux
|
||||
''');
|
||||
|
||||
final FlutterManifest manifest = FlutterManifest.createFromString('''
|
||||
name: test
|
||||
version: 1.0.0
|
||||
|
||||
dependencies:
|
||||
url_launcher: ^1.0.0
|
||||
user_selected_url_launcher_implementation: ^1.0.0
|
||||
''', logger: BufferLogger.test())!;
|
||||
|
||||
flutterProject.manifest = manifest;
|
||||
|
||||
await injectPlugins(flutterProject, linuxPlatform: true);
|
||||
|
||||
final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
|
||||
|
||||
expect(registrantImpl.existsSync(), isTrue);
|
||||
expect(registrantImpl.readAsStringSync(), contains('user_selected_url_launcher_linux_register_with_registrar'));
|
||||
expect(registrantImpl.readAsStringSync(), isNot(contains('inline_url_launcher_linux_register_with_registrar')));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testUsingContext('user-selected implementation overrides default implementation', () async {
|
||||
final List<Directory> directories = createFakePlugins(fs, <String>[
|
||||
'user_selected_url_launcher_implementation',
|
||||
'url_launcher',
|
||||
'url_launcher_linux',
|
||||
]);
|
||||
|
||||
// Add inline native implementation to `user_selected_url_launcher_implementation`
|
||||
directories[0].childFile('pubspec.yaml').writeAsStringSync('''
|
||||
flutter:
|
||||
plugin:
|
||||
implements: url_launcher
|
||||
platforms:
|
||||
linux:
|
||||
pluginClass: UserSelectedUrlLauncherLinux
|
||||
''');
|
||||
|
||||
// Add default native implementation to `url_launcher`
|
||||
directories[1].childFile('pubspec.yaml').writeAsStringSync('''
|
||||
flutter:
|
||||
plugin:
|
||||
platforms:
|
||||
linux:
|
||||
default_package: url_launcher_linux
|
||||
''');
|
||||
|
||||
// Add inline native implementation to `url_launcher_linux`
|
||||
directories[1].childFile('pubspec.yaml').writeAsStringSync('''
|
||||
flutter:
|
||||
plugin:
|
||||
platforms:
|
||||
linux:
|
||||
pluginClass: InlineUrlLauncherLinux
|
||||
''');
|
||||
|
||||
final FlutterManifest manifest = FlutterManifest.createFromString('''
|
||||
name: test
|
||||
version: 1.0.0
|
||||
|
||||
dependencies:
|
||||
url_launcher: ^1.0.0
|
||||
user_selected_url_launcher_implementation: ^1.0.0
|
||||
''', logger: BufferLogger.test())!;
|
||||
|
||||
flutterProject.manifest = manifest;
|
||||
|
||||
await injectPlugins(flutterProject, linuxPlatform: true);
|
||||
|
||||
final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
|
||||
|
||||
expect(registrantImpl.existsSync(), isTrue);
|
||||
expect(registrantImpl.readAsStringSync(), contains('user_selected_url_launcher_linux_register_with_registrar'));
|
||||
expect(registrantImpl.readAsStringSync(), isNot(contains('inline_url_launcher_linux_register_with_registrar')));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testUsingContext('Injecting creates generated Linux registrant, but does not include Dart-only plugins', () async {
|
||||
// Create a plugin without a pluginClass.
|
||||
final Directory pluginDirectory = createFakePlugin(fs);
|
||||
@ -1662,6 +1825,7 @@ The Flutter Preview device does not support the following plugins from your pubs
|
||||
expect(plugin.pluginPodspecPath(fs, WindowsPlugin.kConfigKey), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('Symlink failures give developer mode instructions on recent versions of Windows', () async {
|
||||
final Platform platform = FakePlatform(operatingSystem: 'windows');
|
||||
final FakeOperatingSystemUtils os = FakeOperatingSystemUtils('Microsoft Windows [Version 10.0.14972.1]');
|
||||
|
Loading…
x
Reference in New Issue
Block a user