Skip to content

Conversation

@franciscojma86
Copy link
Contributor

@franciscojma86 franciscojma86 commented Jan 11, 2020

Description

As mentioned in #46618, macOS plugins require an iOS no-op implementation and vice versa. This is due to some limitations in the generated .flutter-plugins file.

Updates .flutter-plugin-dependencies to include the plugins list and version information. Supports all platforms. Example:

{
  "info": "This is a generated file; do not edit or check into version control.",
  "plugins": {
    "ios": [
      {
        "name": "e2e",
        "path": "/path/to/plugin",
      },
      {
        "name": "shared_preferences",
        "path": "/path/to/plugin",
      }
    ],
    "macos": [
      {
        "name": "shared_preferences_macos",
        "path": "/path/to/plugin",
      }
    ],
    "web": [
      {
        "name": "shared_preferences_web",
        "path": "/path/to/plugin",
      }
    ]
  }
}

This PR doesn't include changes to the Podfile and gradle templates, since there are still some issues to consider regarding the migration strategy and CI changes.

Related Issues

#46618

Tests

I added the following tests:

Updated tests in plugins-test.dart and plugin-dependencies-test.dart. Still supports tests for current logic.

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].

  • No, no existing tests failed, so this is not a breaking change.

@fluttergithubbot fluttergithubbot added the tool Affects the "flutter" command-line tool. See also t: labels. label Jan 11, 2020
@franciscojma86 franciscojma86 removed the request for review from jmagman January 11, 2020 00:32
@franciscojma86 franciscojma86 changed the title WIP no-ops with json [flutter_tools] Removes the need of a no-op plugin implementations Jan 13, 2020
@franciscojma86
Copy link
Contributor Author

/cc @amirh


import 'dart:async';

import 'package:flutter_tools/src/platform_plugins.dart';
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be a relative import

/// If there aren't any plugins, then the files aren't written to disk. The resulting
/// file looks something like this:
/// {
/// "_info": "// This is a generated file; do not edit or check into version control.",
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 also recommend embedding some sort of version string in the JSON, just in case

Copy link
Contributor

Choose a reason for hiding this comment

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

Also possibly the date it was generated on/ the version of the tool used to generate it

Copy link
Contributor

Choose a reason for hiding this comment

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

See .dart_tool/package_config.json:

  "generated": "2020-01-13T22:42:48.743606Z",
  "generator": "pub",
  "generatorVersion": "2.8.0-dev.0.0.flutter-395daaa3ec"

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

}
}

/// Writes the .flutter-plugins.json file based on the list of plugins.
Copy link

Choose a reason for hiding this comment

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

.flutter-plugins-dependencies since new projects on stable have this file in the .gitignore already.

Copy link
Contributor

Choose a reason for hiding this comment

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

But that file means something different; wouldn't changing the content break a bunch of projects?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed it to .flutter-plugins-dependencies, and won't break since I still add the dependencyGraph to support current projects. Once it's fully deprecated we can just remove it from the json

@stuartmorgan-g
Copy link
Contributor

Was there discussion somewhere about using a single file that I missed while out? The plan I had proposed was to make a file per platform, in the platform directories, which seemed like it would be simpler for each platform to parse, as well as sticking to the current structure of platform-specific information generally being within platform directories. I'm curious what the pro/con evaluation of that vs. the approach here was.

@jonahwilliams
Copy link
Contributor

Was there discussion somewhere about using a single file that I missed while out?

I think we talked briefly about this before break but I've lost context on the discussion.

@blasten
Copy link

blasten commented Jan 14, 2020

I don’t think I was involved in the discussion about deprecating .flutter-plugins.

Unfortunately, in some situations, Android and iOS have project files that depend on the location and format of this file. I’m looking into addressing this specific problem (managing project files) in a separate effort.

Maybe the rest of the platform can have their own file while iOS/Android continue to use .flutter-plugins until we migrate the affected projects.

@jonahwilliams
Copy link
Contributor

Actually, I remember now. I think the context was concerns about how updates to the platform directories (if any) would effect this file. @franciscojma86 correct me if wrong

@stuartmorgan-g
Copy link
Contributor

how updates to the platform directories (if any) would effect this file

This file is generated on demand though; I would expect that for desktop it would be in the ephemeral/ directories, which means not checked in, so I'm not sure what the problem would be.

@franciscojma86
Copy link
Contributor Author

Yeah it was a brief discussion with @jonahwilliams , but I should've opened an issue and discussed there. Looked simple enough at the beginning (doesn't it all? 😄 ).

There were no strong reasons to go either way. The one advantage of going this way, is to stop using the key-value file and turn it into a JSON which is more flexible and we can version. Specially since .flutter-plugins-dependencies is already a JSON file. I actually did a per platform implementation here #47258. It turns out it breaks other tests since it creates the ephemeral folder prematurely. The current implementation has been a bit smoother.

The only advantage I see of doing a per platform .flutter-plugins file is that we wouldn't have to add json parsing logic on the Podfile templates, but I actually already did that in another branch, and it's just a couple extra lines of codes.

@stuartmorgan do you have any concerns with this method? The other PR is not far behind so I can continue that one if people think is a better options. Personally, I don't see any clear advantages.

@stuartmorgan-g
Copy link
Contributor

I have a couple of potential concerns, although nothing definite enough to prevent moving forward:

  • The JSON dependency isn't just for Podfiles, it's for every platform that needs to do something with those files. E.g., if I want to use it on Linux, then I need to do JSON parsing in... a tool called from a Makefile I guess? But since I don't have the details of how it'll be used, that's not a concrete concern, since maybe that's no harder than whatever I would need to do with the key-value version. (Also, .flutter-plugins-dependencies already being JSON may make that moot, since I may need that on other platforms as well.)
  • In a future where platform build details are in separate packages, having them all writing into a single file seems potentially awkward, but that's presumably something we can address by just having one of the APIs be to provide the JSON to put in the platform-specific section.

And I can see the argument for this being potentially more future-proof, since we can add new sections to a JSON file without breaking existing projects. Let's proceed with this, and if it's problematic for Linux or Windows later I can figure out how to adapt.

}
}

/// Writes the .flutter-plugins.json file based on the list of plugins.
Copy link
Contributor

Choose a reason for hiding this comment

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

But that file means something different; wouldn't changing the content break a bunch of projects?

}

void _addPluginsToPlatform(List<Plugin> plugins, FlutterProjectPlatform platform, Map<String, dynamic> pluginsMap) {
if (!platform.existsSync()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If the file is in a central location rather than sharded to the platform directories, why do we need this check?

It's not clear to me that if I add a new platform to an existing project this file would be regenerated unless I also change the plugins, so skipping platforms make cause weird errors for people adding a platform after the fact. And generating them all should be harmless, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I got rid of this and added to the map directly. I was trying to avoid writing platforms that have no plugins but as you say, there's no much point to that. If there are no plugins at all, the file won't be written.

return plugins;
}

void _addPluginsToPlatform(List<Plugin> plugins, FlutterProjectPlatform platform, Map<String, dynamic> pluginsMap) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you document this? It's not entirely obvious to me how the arguments interact without looking at a bunch of code (e.g., that pluginsMap is basically an out param).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed it

const String info = 'This is a generated file; do not edit or check into version control.';

final Map<String, dynamic> result = <String, dynamic> {};
result['_info'] = '// $info';
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this have a C-style comment prefix, when it's just a string value in a JSON?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Bad copy paste :)

}
}

return oldPluginFileContent != _readFileContent(pluginsFile);
Copy link
Contributor

Choose a reason for hiding this comment

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

Since there's encoding/decoding going on now, it would be nice to make this a little bit more efficient. You don't need to json.encode above if pluginsMap.isEmpty, and you don't need to do the file readback since you already have pluginFileContent. There's already branching for empty vs. non-empty, so being more efficient here would add minimal complexity. Something like:

  final File pluginsFile = project.flutterPluginsJsonFile;
  final String oldPluginFileContent = _readFileContent(pluginsFile);
  final String pluginFileContent = pluginsMap.isNotEmpty ? json.encode(result) : null;
  if (pluginFileContent != null) {
    pluginsFile.writeAsStringSync(pluginFileContent, flush: true);
  } else {
    if (pluginsFile.existsSync()) {
      pluginsFile.deleteSync();
    }
  }

  return pluginFileContent != oldPluginFileContent;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved the check for empty plugins to the top. Feels a bit cleaner to me. WDYT?

bool existsSync();

/// Creates a list with the project plugins supported for the current platform.
List<Map<String, dynamic>> pluginsList(List<Plugin>plugins) {
Copy link
Contributor

Choose a reason for hiding this comment

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

filterPlugins? The current name suggests to me that it's returning a list of plugins that are a property of the project, rather than being driven by the input list. (It feels a bit odd for this to be part of the project, rather than a stand-alone utility or something on a plugins class, but I don't feel super strongly about that.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree it feels better as a helper function. Done

@stuartmorgan-g
Copy link
Contributor

Maybe the rest of the platform can have their own file while iOS/Android continue to use .flutter-plugins until we migrate the affected projects.

Isn't that why the legacy file is still being written in this patch? We should still be creating the new file for all platforms so there's something to migrate to later.

Copy link
Contributor

@stuartmorgan-g stuartmorgan-g left a comment

Choose a reason for hiding this comment

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

Just small nits; LGTM

/// If there aren't any plugins, then the files aren't written to disk. The resulting
/// file looks something like this:
/// {
/// "info": "This is a generated file; do not edit or check into version control.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't the location of this line in the file going to be "random" (not from run to run unless the map implementation changes, but not guaranteed to be first)? I don't have a solution that doesn't break existing apps since we're re-using a file that's already a dictionary--otherwise we could wrap the real content in an array with the comment then the content--but I'm wondering how useful this will end up being without an order dependency. I guess better than nothing...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed the test to not require an order comparison and clarified in the comment that the order is not guaranteed

return directAppDependencies;
}

// This method will be DEPRECATED in favor of _writeFlutterPluginsList.
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this isn't an API surface, it won't ever actually be deprecated, will it, just removed? The API is the existence of the file, not the method.

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

'plugin-c=/.tmp_rand0/plugin.rand2/\n'
''
);
expect(flutterProject.flutterPluginsDependenciesFile.readAsStringSync(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we actually want this test to break if the order of the elements in this file change? I.e., do we consider the order of elements in a JSON dictionary to be an API? That seems extreme; I know in some cases Ian has advocated for that, but for a file that's only intended for use as JSON I'm not sure we actually want that.

If we don't, this test should parse and then do a deep structure equality test, instead of comparing strings.

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 test as discussed


// https://github.com/flutter/flutter/issues/46898
// https://github.com/flutter/flutter/issues/39657
File(path.join(pluginCDirectory.path, 'android', 'build.gradle')).deleteSync();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed this line since the top issue is closed and the second one I assume would be fixed by this PR.

@fluttergithubbot fluttergithubbot added the c: contributor-productivity Team-specific productivity, code health, technical debt. label Jan 15, 2020
@franciscojma86
Copy link
Contributor Author

@blasten @jonahwilliams I updated it to include the plugins list in .flutter-plugins-dependencies rather than replacing it. Updated a device lab test as well. PTAL!

/// "windows": [],
/// "web": []
/// },
/// "dependencyGraph": [
Copy link

Choose a reason for hiding this comment

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

Is the plan to split "dependencyGraph" per platform in a separate PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think it makes much sense to split it anymore. It doesn't really add any value. I started that way, but then we'd have change the way the dependencies are handled in gradle, which at that point felt unnecessary. What do you think?

Copy link

Choose a reason for hiding this comment

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

Gradle is working around the fact that dependencyGraph isn't split per platform. I think it would be ideal if Gradle didn't have to workaround that and instead just read this file. This could be done in a separate PR though if that 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.

Makes sense, I did it here :)

Copy link

@blasten blasten left a comment

Choose a reason for hiding this comment

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

LGTM! + comment

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

Labels

c: contributor-productivity Team-specific productivity, code health, technical debt. tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants