Skip to content

Conversation

@cyanglaz
Copy link
Contributor

@cyanglaz cyanglaz commented Jun 15, 2020

Description

Adds a --platforms flag to the flutter create. The flag only works in -t plugin and -t app
This Pr doesn't make --platforms support web yet because web template is not ready.

Note: We need to decide what to do with the version constraint before landing this.

Related Issues

#59493

Tests

plugin supports ios if requested
plugin supports android if requested
create an empty plugin, then add ios
create an empty plugin, then add android
create an empty plugin, then add linux
create an empty plugin, then add macos
create an empty plugin, then add windows
create a plugin with ios, then add macos
create a plugin with ios and android
create a module with --platforms throws error.
create a package with --platforms throws error.

Also updated some existing tests to fit the new flag.

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.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Did any tests fail when you ran them? Please read Handling breaking changes.

@fluttergithubbot fluttergithubbot added tool Affects the "flutter" command-line tool. See also t: labels. work in progress; do not review labels Jun 15, 2020
@cyanglaz cyanglaz changed the title Refactor create.dart so it can share code with add-platform Add flutter add-platform command Jun 17, 2020
@cyanglaz cyanglaz marked this pull request as ready for review June 17, 2020 20:47
Copy link
Contributor

@jonahwilliams jonahwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is really hard to review, because I'm not really sure which code was changed and which was original. I'm not convinced that doing a massive refactor ahead of trying to change the behavior is the best way to go about this.

Could I suggest a more incremental approach: make the methods you need to override on create visibleForTesting, then create an AddPackage command that extends flutter create.

Then, you could gradually refactor shared functionality with huge diffs

/// Perform an initial validation of the environment and args.
///
/// Should be called as early as possible in [FlutterCommand.runCommand].
void doInitialValidation() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already a hook for this: FlutterCommand.verifyThenRunCommand

@cyanglaz cyanglaz changed the title Add flutter add-platform command Add --platforms to flutter create -t plugin command Jun 18, 2020
/// Adding argument is optional if the command does not require the user to explicitly state what platforms the project supports.
void _addPlatformsOptions() {
argParser.addMultiOption('platforms',
help: 'the platforms supported by this project.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expand this comment a bit for each kind of template. Talk about what it does/does not generate

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated, see if you like this.

globals.printError('''
You have generated a new plugin project without
specifying the `--platforms` flag. A plugin project supports no platforms is generated.
The project will not compile until you add platforms implementation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

project -> plugin?

The project will not compile until you add platforms implementation.

This isn't quite true, it just isn't useful

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or remove this line?

generatedCount += await _renderTemplate('plugin', directory, templateContext, overwrite: overwrite);
if (!argResults.wasParsed('platforms')) {
globals.printError('''
You have generated a new plugin project without
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you hoist this out into a constant so it is easier to read?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move to the top of the file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

context['android'] = true;
break;
case 'web':
context['web'] = featureFlags.isWebEnabled && true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than conditionally checking here, you might be able to only add web, linux, macos, windows to the list itself if the experiment is enabled. Then you can do:

for (final String platform in platforms) {
  context[platform] = true;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated with a slightly different approach, please take a look and see if you like it.

Copy link
Contributor Author

@cyanglaz cyanglaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonahwilliams I updated the PR with some of your suggestions. I'll work on the other half of the comments and ping you once they are all addressed.

globals.printError('''
You have generated a new plugin project without
specifying the `--platforms` flag. A plugin project supports no platforms is generated.
The project will not compile until you add platforms implementation.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or remove this line?

generatedCount += await _renderTemplate('plugin', directory, templateContext, overwrite: overwrite);
if (!argResults.wasParsed('platforms')) {
globals.printError('''
You have generated a new plugin project without
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/// Adding argument is optional if the command does not require the user to explicitly state what platforms the project supports.
void _addPlatformsOptions() {
argParser.addMultiOption('platforms',
help: 'the platforms supported by this project.',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated, see if you like this.

if (argResults.wasParsed('platforms') && (generateModule || generatePackage)) {
final String template = generateModule?'module':'package';
throwToolExit(
'The "--platforms" argument is not supported in $template template. please remove the argument and try again.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, remove : please remove the argument and try again

final List<String> platforms = stringsArg('platforms');
// `--platforms` does not support module and package.
if (argResults.wasParsed('platforms') && (generateModule || generatePackage)) {
final String template = generateModule?'module':'package';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


final List<String> platforms = _getSupportedPlatformsFromTemplateContext(templateContext);
String platformsString = '';
for (int i = 0; i < platforms.length; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

platforms.join(',') ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. Good call!

argParser.addMultiOption('platforms',
help: 'the platforms supported by this project.'
'This argument only works when the --template is set to app or plugin.'
'Respective folders will be generated in the target project.'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clarify "respective folders" - "platform folders (android/ ) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

'Respective folders will be generated in the target project.'
'When adding platforms to a plugin project, the pubspec.yaml will be updated with the requested platform.'
'When adding windows, linux or macos platforms, the platform feature has to be enable. Otherwise it would be a no-op.'
'To enable desktop features, run `flutter config --enable-<desktopName>-desktop.'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe: Adding desktop platforms requires the corresponding desktop config setting to be enabled

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

generatedCount += await _renderTemplate('plugin', directory, templateContext, overwrite: overwrite);
if (!argResults.wasParsed('platforms')) {
globals.printError('''
You have generated a new plugin project without
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move to the top of the file

if (boolArg('pub')) {
await pub.get(context: PubContext.create, directory: directory.path, offline: boolArg('offline'));
await project.ensureReadyForPlatformSpecificTooling(checkProjects: false);
await project.ensureReadyForPlatformSpecificTooling(checkProjects: pluginExampleApp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is confusing, why do we need to only check projects if we're creating an example app

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a side effect, this method always generates the GeneratedPluginRegstraint files for iOS and Android unless the checkProject flag is true. When the plugin is not for mobile, this method still generate those files, including iOS and Android.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(ew.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you guys think pluginExampleApp is confusing? I am thinking it might be better just to call it checkProjects or checkProjectsWhileEnsuringTooling

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the non specificity of "checkProjects" is what's throwing me off (I know you didn't name it). Like, check what? If that was a better name, pluginExampleApp might make sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By looking at the code of ensureReadyForPlatformSpecificTooling, I also not sure what that meant. @jonahwilliams do you have any idea? Maybe we can add a TODO comment on that method to either fix the param name or add some doc/comments on the checkProjects param?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, TODO for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return generatedCount;
}

void _updateTemplateContextWithPlatforms(Map<String, dynamic> context, List<String> platforms) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is confusing, everything gets set to false by default, then everything get sets to true, then some are conditional set to false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right, I deleted this method and updated the caller a bit. Please take a look see if it makes more sense.

///
/// Throws ToolExit if cannot find the `platforms:` map.
static YamlMap getPlatformsYamlMap(YamlMap pubspec) {
if (pubspec == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming that someone is migrating an old plugin and trying to use add-platform, it would be nice to at least link to page explaining the formatting

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. We only need to show this error if the platforms map is missing or not valid, other failing scenarios are just general invalid pubspec.

throwToolExit('Did not find valid `platforms` map in pubspec.yaml', exitCode: 2);
}

return pluginConfig['platforms'] as YamlMap;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing a type check here, platforms could be anything

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

final List<String> platformsToAdd = List<String>.from(platforms);
final YamlMap platformsMap = getPlatformsYamlMap(pubspec);
final List<String> existingPlatforms = platformsMap.keys.cast<String>().toList();
platformsToAdd.removeWhere((String platform) => existingPlatforms.contains(platform));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

platformsToAdd.removeWhere(existingPlatforms.contains);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

frontSpaces = line.split('platforms:').first;
index = i + 1;
}
if (line.contains('some_platform:')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this rely on on having a fake platform key?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic doesn't. however it does remove the some_platform.
The reason that some_platform needs to be there is that we need to keep the pubspec.yaml valid. Otherwise, other steps of the create command would fail (pub get for example)

Another way is to perhaps not having the whole flutter map in the pubspec if there are no --platforms specified, then add the flutter map programmatically when --platforms is specified.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems pretty fragile, what if someone develops android/ios and then wants to use add-platform to add a new desktop or web implementation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also work as the code is to look for the platforms: key and add an entry under it. i think I have a test covers this scenario: 'create a plugin with ios, then add macos'.
It only removes 'some_platform' map, which is an invalid platform.

/// ```
///
/// This method doesn't support the legacy plugin pubspec format and will throw error if try to update a pubspec.yaml that uses the legacy format.
static Future<void> updatePubspecWithPlatforms(String projectDir, List<String> platforms, String pluginClass, String androidIdentifier) async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like there are a lot of ways for this method to fail, and it requires a sentinel data in the form of the comment. Is there a way this process could be simplified by ignoring comments? Is add-platforms going to be usable if it requires a very specific structure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one way to improve this is to ignore the lines that starts with a #

Other than that, I don't find a perfect solution to update this without breaking the existing yaml comments. Which I think we need to preserve because those are great instructions for new users.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say ignore the comments and focus on the structured data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to overwrite all the comments when updating pubspec.yaml?
Something like
file.write(pubspecYamlMap.convertToString)?
(I'm making up APIs here)

Or did you mean to skip comments like

if (line.contains('#')) {
  continue;
}

Copy link
Member

@jmagman jmagman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@flutter-github-sync
Copy link

Google testing passed!

3 similar comments
@flutter-github-sync
Copy link

Google testing passed!

@flutter-github-sync
Copy link

Google testing passed!

@flutter-github-sync
Copy link

Google testing passed!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants