Repository navigation
Flutter Plugin Tool supports multi-platform plugin config - #38632
Conversation
This addresses flutter#10288 partially. I'm currently re-using a Plugin pojo. There will be some follow up work here: 1. Migrate the current pojo to have a `Set<PlatformPlugin>` and have impls for: `AndroidPlatformPlugin`, `IOSPlatformPlugin`, `MacOSPlatformPlugin`. They can each contain their own fields. 2. Add more meaningful error messages to the tool when we encounter any invalid configurations. Currently there is not much error handling done. 3. Address flutter#38628
|
@amirh i'm staging this PR to get any initial feedback you had about the vision. I do not intend to land it in its current state. I will have follow-up work to address the things listed in the PR description. |
| /// Parses [Plugin] specification from the provided pluginYaml. | ||
| /// | ||
| /// This currently supports two formats. V1 (Legacy) and V2 (Multi-platform). | ||
| /// Example V1 format. Which is deprecated. |
There was a problem hiding this comment.
V1 and V2 might be confusing. Might be better to stick with just Legacy and MultiPlatform as the canonical names for these.
There was a problem hiding this comment.
legacy and multiplat sounds reasonable.
| if (androidConfig['pluginClass'] != null) { | ||
| pluginClassNames.add(androidConfig['pluginClass']); | ||
| } else { | ||
| throw Exception('"pluginClass" needs to be specified for android platform'); |
There was a problem hiding this comment.
might be worth creating a new exception type for failures relating to parsing a plugin. I haven't seen any custom exceptions in the flutter_tools project so I stuck with Exception.
|
I'm also changing the current parameters and namespacing them as they relate to the new format:
|
jonahwilliams
left a comment
There was a problem hiding this comment.
This approach as written seems to require only well known keys, but platforms themselves are an open set. Furthermore, we'd also like each platform to be able to require/supply its own keys, so the main manifest class should probably not be responsible for parsing them.
| } | ||
|
|
||
| /// Contains all the required parameters to template an Android plugin. | ||
| class AndroidPlugin extends PlatformPlugin { |
There was a problem hiding this comment.
This seems like a better approach for splitting up the plugins. It would also be helpful to add some sort of unknown/unspecified platform that handles keys we don't recognize.
| }; | ||
| } | ||
|
|
||
| static const String kConfigKey = 'android'; |
There was a problem hiding this comment.
nit, place the fields above the first method.
| } | ||
|
|
||
| /// Contains all the required parameters to template an Android plugin. | ||
| class AndroidPlugin extends PlatformPlugin { |
There was a problem hiding this comment.
For the well known plugin types, please add documentation for the expected keys and their meaning
| platformPlugins[MacOSPlugin.kConfigKey] = | ||
| MacOSPlugin.fromYaml(name, platforms[MacOSPlugin.kConfigKey]); | ||
| } | ||
|
|
There was a problem hiding this comment.
I would restructure this so that we can grab unspecified platforms as well as known platforms.
Codecov Report
@@ Coverage Diff @@
## master #38632 +/- ##
=========================================
- Coverage 56% 55.2% -0.8%
=========================================
Files 195 196 +1
Lines 18231 18285 +54
=========================================
- Hits 10210 10094 -116
- Misses 8021 8191 +170
Continue to review full report at Codecov.
|
1 similar comment
Codecov Report
@@ Coverage Diff @@
## master #38632 +/- ##
=========================================
- Coverage 56% 55.2% -0.8%
=========================================
Files 195 196 +1
Lines 18231 18285 +54
=========================================
- Hits 10210 10094 -116
- Misses 8021 8191 +170
Continue to review full report at Codecov.
|
Codecov Report
@@ Coverage Diff @@
## master #38632 +/- ##
==========================================
+ Coverage 56.54% 56.78% +0.24%
==========================================
Files 195 196 +1
Lines 18471 18556 +85
==========================================
+ Hits 10444 10537 +93
+ Misses 8027 8019 -8
Continue to review full report at Codecov.
|
|
@stuartmorgan , I have removed all references to macOS in legacy format, i did not realize that none of them were published. I also included macOS plugin in the test that I wrote. @jonahwilliams , I have added the documentation about the required fields for the plugins. I have also filed #38853 to address support for unknown/unsupported plugins. I will add support for them in a follow-up PR. I have also resolved the other comments you have given me. PTAL and thanks! |
| @required this.pluginClass, | ||
| }); | ||
|
|
||
| factory AndroidPlugin.fromYaml(String name, dynamic yaml) { |
There was a problem hiding this comment.
This should be a YamlMap to work right?
There was a problem hiding this comment.
It also works with dynamic yaml. Changing it to YamlMap yaml does make it better. Fixed!
stuartmorgan-g
left a comment
There was a problem hiding this comment.
LGTM, but please wait for @jonahwilliams's approval as well.
I'll try to prep the FDE change that will handle the macOS breaking change tonight.
stuartmorgan-g
left a comment
There was a problem hiding this comment.
Sorry, a couple of things I missed that I noticed while doing the FDE change:
- This check needs to be updated. I think it's fine to just remove the comment and s/macosPrefix/macos/. Ideally we should do a full parse to avoid possible collisions, but at this point there's no other 'macos' key so I think we can keep it simple for now.
- While searching for any other places that might be referenced I found this schema, which should be updated too.
Updates all plugins to conform to the new plugin schema in flutter/flutter#38632
amirh
left a comment
There was a problem hiding this comment.
LGTM overall, left a few nits.
| Map<String, dynamic> toMap() { | ||
| return <String, dynamic>{ | ||
| 'name': name, | ||
| 'prefix': classPrefix, |
There was a problem hiding this comment.
Now is a good opportunity to get rid of this prefix as well if we want to do so, I'm not sure what is it useful for...
There was a problem hiding this comment.
Though we won't be breaking any existing code if we do it right now, we would make the migration that have to happen anyway harder (I'd think not by much).
On a related note - Stuart do you think it would be useful to have a (potentially optional) fileName parameter here and for macOS?
There was a problem hiding this comment.
Though we won't be breaking any existing code if we do it right now, we would make the migration that have to happen anyway harder (I'd think not by much).
I don't follow. If you remove the parameter, then it's impossible to describe existing ObjC plugins using the new format. Are you suggesting we require everyone to rename their source files when migrating?
On a related note - Stuart do you think it would be useful to have a (potentially optional)
fileNameparameter here and for macOS?
It's not clear to me how replacing classPrefix with fileName helps; it seems like the net effect is that we are still stuck with an extra parameter that we would rather not have, but with the added downside that migration between old and new formats is less obvious.
I don't want it for macOS; I can't see any compelling non-legacy reason to support custom filenames for source files when the convention of filename=class name is simple, and widely used in general.
There was a problem hiding this comment.
Though we won't be breaking any existing code if we do it right now, we would make the migration that have to happen anyway harder (I'd think not by much).
I don't follow. If you remove the parameter, then it's impossible to describe existing ObjC plugins using the new format. Are you suggesting we require everyone to rename their source files when migrating?
Yes.
On a related note - Stuart do you think it would be useful to have a (potentially optional)
fileNameparameter here and for macOS?It's not clear to me how replacing
classPrefixwithfileNamehelps; it seems like the net effect is that we are still stuck with an extra parameter that we would rather not have, but with the added downside that migration between old and new formats is less obvious.I don't want it for macOS; I can't see any compelling non-legacy reason to support custom filenames for source files when the convention of filename=class name is simple, and widely used in general.
Makes sense, I didn't have a preference here, just wanted to check because if we would want to eventually support that we should not introduce extra migration pain right now.
There was a problem hiding this comment.
If you're okay with including a file rename step in the migration for most existing plugins, then I would agree with removing classPrefix. (The plugin template for language=objc would need to be updated accordingly.) That would need to be prominently documented somewhere.
There was a problem hiding this comment.
Let's start without classPrefix, when this PR makes it to stable we essentially start a "soft migration" period (as we still support the old schema). If we learn that the migration is too painful we could later add classPrefix (it shouldn't be breaking for plugins that already migrated without classPrefix).
We will definitely need documentation and announcements around this migration.
There was a problem hiding this comment.
Let's start without
classPrefix
Then we'll need to change the objc plugin template (even though it's no longer the default, it can still be selected) to create the files with the prefixes included in the same PR that changes the pubspec.yaml in the template. (Which doesn't need to be this PR, but if it's not going to be, then let's get an issue tracking that so it doesn't get forgotten.)
| // an indicator of macOS support, see https://github.com/flutter/flutter/issues/33597 | ||
| macosPrefix = pluginYaml['macosPrefix']; | ||
| pluginClass = pluginYaml['pluginClass']; | ||
| // TODO(kaushikiska): Fail if both old and new formats are present. |
There was a problem hiding this comment.
We should get to this early, before this change breaks anyone.
| final String iosPrefix; | ||
| final String macosPrefix; | ||
| final String pluginClass; | ||
| final Map<String, PlatformPlugin> platformPlugins; |
There was a problem hiding this comment.
nit: sounds a little weird for a plugin to have a set of "platform plugins" maybe we should call this field platforms, and the type PluginPlatform?
| final String iosPrefix; | ||
| final String macosPrefix; | ||
| final String pluginClass; | ||
| final Map<String, PlatformPlugin> platformPlugins; |
There was a problem hiding this comment.
nit: document that this is mapping a platform key to the plugin's platform spec.
| } | ||
| '''; | ||
|
|
||
| List<Map<String, dynamic>> _filterPlugins(List<Plugin> plugins, String type) { |
There was a problem hiding this comment.
nit: Was a little unclear what this is filtering for from the name, and it also does more than filtering. Maybe call this _extractPlatformMaps ?
| '''; | ||
|
|
||
| List<Map<String, dynamic>> _filterPlugins(List<Plugin> plugins, String type) { | ||
| final List<Map<String, dynamic>> pluginConfigs = <Map<String, dynamic>>[]; |
There was a problem hiding this comment.
nit: you may be able to do this nicely with list operators if you like them e.g:
final List<Plugin> filteredPlugins = plugins.retainWhere((Plugin p) => p.platforms.containsKey(platform));
return filteredPlugins.map((Plugin p) => p.toMap());There was a problem hiding this comment.
This does not return a List, it returns an Iterable
There was a problem hiding this comment.
.toList? (and update the first type)
There was a problem hiding this comment.
I find the current implementation quite easy to follow, leaving it as is.
Manifest now recognizes the new format as well.
| package: com.example | ||
| pluginClass: MyPlugin | ||
| ios: | ||
| prefix: FLT |
Updates all plugins to conform to the new macOS plugin schema in flutter/flutter#38632
| } | ||
| if (pluginYaml != null && pluginYaml['platforms'] != null) { | ||
| return Plugin._fromMultiPlatformYaml(name, path, pluginYaml); | ||
| } else { |
| } | ||
|
|
||
| static List<String> validatePluginYaml(YamlMap yaml) { | ||
| if (yaml.containsKey('platforms')) { |
There was a problem hiding this comment.
Flip the sense and un-indent the larger block:
if (!yaml.containsKey('platform')) {
return _validateLegacyYaml(yaml);
}
// no else, un-indent.| return <String>[ | ||
| 'Invalid plugin specification. There must be only one key: "platforms", found multiple: ${yaml.keys.join(',')}' | ||
| ]; | ||
| } else { |
| 'Invalid plugin specification. There must be only one key: "platforms", found multiple: ${yaml.keys.join(',')}' | ||
| ]; | ||
| } else { | ||
| return _validateMultiPlatformYaml(yaml['platforms']); |
There was a problem hiding this comment.
If yaml['platforms'] is not a YamlMap, then this call will throw a runtime type error. Please add a check, and return an error instead.
| static List<String> _validateMultiPlatformYaml(YamlMap yaml) { | ||
| final List<String> errors = <String>[]; | ||
| if (yaml.containsKey(AndroidPlugin.kConfigKey) && | ||
| !AndroidPlugin.validate(yaml[AndroidPlugin.kConfigKey])) { |
There was a problem hiding this comment.
ditto. Check the type of yaml[AndroidPlugin.kConfigKey] etc. and add appropriate error messages.
| name: test | ||
| flutter: | ||
| plugin: | ||
| platforms: |
There was a problem hiding this comment.
Please add tests that cover the cases where these maps are malformed, esp as noted above where they are not maps.
Closes #38632. Mostly clarifies how validation works, and reuses some code.
…lutter#171461) Closes flutter#38632. Mostly clarifies how validation works, and reuses some code.
…lutter#171461) Closes flutter#38632. Mostly clarifies how validation works, and reuses some code.
Closes flutter/flutter#38632. Mostly clarifies how validation works, and reuses some code.
Description
This addresses #10288 partially.
I'm currently re-using a Plugin pojo. There will be some follow up work
here:
[DONE] Migrate the current pojo to have a
Map<String, PlatformPlugin>and haveimpls for:
AndroidPlatformPlugin,IOSPlatformPlugin,MacOSPlatformPlugin. They can each contain their own fields.Add more meaningful error messages to the tool when we encounter any
invalid configurations. Currently there is not much error handling done.
[DONE] Address Flutter Tool currently does not allow different plugin class names for android and ios impls #38628
Related Issues
Tests
I added the following tests:
plugin_parsing_test.dart.Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]). This will ensure a smooth and quick review process.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Does your PR require Flutter developers to manually update their apps to accommodate your change?