Add `.flutter-plugins-dependencies` to the project, which contains the app's plugin dependency graph (#45379)
diff --git a/.gitignore b/.gitignore
index b48f326..c407f64 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,6 +44,7 @@
**/doc/api/
.dart_tool/
.flutter-plugins
+.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/.gitignore b/dev/integration_tests/android_embedding_v2_smoke_test/.gitignore
index 437cb45..ae1f183 100644
--- a/dev/integration_tests/android_embedding_v2_smoke_test/.gitignore
+++ b/dev/integration_tests/android_embedding_v2_smoke_test/.gitignore
@@ -24,6 +24,7 @@
**/doc/api/
.dart_tool/
.flutter-plugins
+.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
diff --git a/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/.gitignore b/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/.gitignore
index 05fc69b..2b1f495 100644
--- a/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/.gitignore
+++ b/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/.gitignore
@@ -27,6 +27,7 @@
**/doc/api/
.dart_tool/
.flutter-plugins
+.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
diff --git a/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/.gitignore b/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/.gitignore
index 716b5a9..7391168 100644
--- a/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/.gitignore
+++ b/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/.gitignore
@@ -24,6 +24,7 @@
**/doc/api/
.dart_tool/
.flutter-plugins
+.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
diff --git a/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/.gitignore b/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/.gitignore
index 716b5a9..7391168 100644
--- a/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/.gitignore
+++ b/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/.gitignore
@@ -24,6 +24,7 @@
**/doc/api/
.dart_tool/
.flutter-plugins
+.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
diff --git a/dev/integration_tests/ios_add2app/flutterapp/.gitignore b/dev/integration_tests/ios_add2app/flutterapp/.gitignore
index cdecf14..86f4691 100644
--- a/dev/integration_tests/ios_add2app/flutterapp/.gitignore
+++ b/dev/integration_tests/ios_add2app/flutterapp/.gitignore
@@ -39,3 +39,4 @@
.android/
.ios/
.flutter-plugins
+.flutter-plugins-dependencies
diff --git a/dev/integration_tests/release_smoke_test/.gitignore b/dev/integration_tests/release_smoke_test/.gitignore
index 716b5a9..7391168 100644
--- a/dev/integration_tests/release_smoke_test/.gitignore
+++ b/dev/integration_tests/release_smoke_test/.gitignore
@@ -24,6 +24,7 @@
**/doc/api/
.dart_tool/
.flutter-plugins
+.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle
index 45dc4e3..173ba30 100644
--- a/packages/flutter_tools/gradle/flutter.gradle
+++ b/packages/flutter_tools/gradle/flutter.gradle
@@ -6,6 +6,7 @@
import com.android.builder.model.AndroidProject
import com.android.build.OutputFile
+import groovy.json.JsonSlurper
import java.nio.file.Path
import java.nio.file.Paths
import java.util.regex.Matcher
@@ -253,6 +254,7 @@
private void configurePlugins() {
if (!buildPluginAsAar()) {
getPluginList().each this.&configurePluginProject
+ getPluginDependencies().each this.&configurePluginDependencies
return
}
project.repositories {
@@ -298,19 +300,15 @@
}
// Adds the plugin project dependency to the app project .
- private void configurePluginProject(String name, String _) {
- Project pluginProject = project.rootProject.findProject(":$name")
+ private void configurePluginProject(String pluginName, String _) {
+ Project pluginProject = project.rootProject.findProject(":$pluginName")
if (pluginProject == null) {
- project.logger.error("Plugin project :$name not found. Please update settings.gradle.")
+ project.logger.error("Plugin project :$pluginName not found. Please update settings.gradle.")
return
}
// Add plugin dependency to the app project.
project.dependencies {
- if (project.getConfigurations().findByName("implementation")) {
- implementation pluginProject
- } else {
- compile pluginProject
- }
+ implementation pluginProject
}
Closure addEmbeddingCompileOnlyDependency = { buildType ->
String flutterBuildMode = buildModeFor(buildType)
@@ -337,6 +335,36 @@
}
}
+ // Add the dependencies on other plugin projects to the plugin project.
+ // A plugin A can depend on plugin B. As a result, this dependency must be surfaced by
+ // making the Gradle plugin project A depend on the Gradle plugin project B.
+ private void configurePluginDependencies(Object dependencyObject) {
+ assert dependencyObject.name instanceof String
+ Project pluginProject = project.rootProject.findProject(":${dependencyObject.name}")
+ if (pluginProject == null) {
+ // Ignore plugins that don't have a project since most likely they don't
+ // have an android/ directory.
+ return
+ }
+ assert dependencyObject.dependencies instanceof List
+ dependencyObject.dependencies.each { pluginDependencyName ->
+ assert pluginDependencyName instanceof String
+ if (pluginDependencyName.empty) {
+ return
+ }
+ Project dependencyProject = project.rootProject.findProject(":$pluginDependencyName")
+ if (dependencyProject == null) {
+ return
+ }
+ // Wait for the Android plugin to load and add the dependency to the plugin project.
+ pluginProject.afterEvaluate {
+ pluginProject.dependencies {
+ implementation dependencyProject
+ }
+ }
+ }
+ }
+
private Properties getPluginList() {
File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins')
Properties allPlugins = readPropertiesIfExist(pluginsFile)
@@ -353,6 +381,39 @@
return androidPlugins
}
+ // Gets the plugins dependencies from `.flutter-plugins-dependencies`.
+ private List getPluginDependencies() {
+ // Consider a `.flutter-plugins-dependencies` file with the following content:
+ // {
+ // "dependencyGraph": [
+ // {
+ // "name": "plugin-a",
+ // "dependencies": ["plugin-b","plugin-c"]
+ // },
+ // {
+ // "name": "plugin-b",
+ // "dependencies": ["plugin-c"]
+ // },
+ // {
+ // "name": "plugin-c",
+ // "dependencies": []'
+ // }
+ // ]
+ // }
+ //
+ // This means, `plugin-a` depends on `plugin-b` and `plugin-c`.
+ // `plugin-b` depends on `plugin-c`.
+ // `plugin-c` doesn't depend on anything.
+ File pluginsDependencyFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins-dependencies')
+ if (pluginsDependencyFile.exists()) {
+ def object = new JsonSlurper().parseText(pluginsDependencyFile.text)
+ assert object instanceof Map
+ assert object.dependencyGraph instanceof List
+ return object.dependencyGraph
+ }
+ return []
+ }
+
private static String toCammelCase(List<String> parts) {
if (parts.empty) {
return ""
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index 2be328c..7e9b124 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -4,12 +4,14 @@
import 'dart:async';
+import 'package:meta/meta.dart';
import 'package:mustache/mustache.dart' as mustache;
import 'package:yaml/yaml.dart';
import 'android/gradle.dart';
import 'base/common.dart';
import 'base/file_system.dart';
+import 'convert.dart';
import 'dart/package_map.dart';
import 'features.dart';
import 'globals.dart';
@@ -27,10 +29,14 @@
class Plugin {
Plugin({
- this.name,
- this.path,
- this.platforms,
- });
+ @required this.name,
+ @required this.path,
+ @required this.platforms,
+ @required this.dependencies,
+ }) : assert(name != null),
+ assert(path != null),
+ assert(platforms != null),
+ assert(dependencies != null);
/// Parses [Plugin] specification from the provided pluginYaml.
///
@@ -60,18 +66,28 @@
/// pluginClass: SamplePlugin
/// windows:
/// pluginClass: SamplePlugin
- factory Plugin.fromYaml(String name, String path, YamlMap pluginYaml) {
+ factory Plugin.fromYaml(
+ String name,
+ String path,
+ YamlMap pluginYaml,
+ List<String> dependencies,
+ ) {
final List<String> errors = validatePluginYaml(pluginYaml);
if (errors.isNotEmpty) {
throwToolExit('Invalid plugin specification.\n${errors.join('\n')}');
}
if (pluginYaml != null && pluginYaml['platforms'] != null) {
- return Plugin._fromMultiPlatformYaml(name, path, pluginYaml);
+ return Plugin._fromMultiPlatformYaml(name, path, pluginYaml, dependencies);
}
- return Plugin._fromLegacyYaml(name, path, pluginYaml);
+ return Plugin._fromLegacyYaml(name, path, pluginYaml, dependencies);
}
- factory Plugin._fromMultiPlatformYaml(String name, String path, dynamic pluginYaml) {
+ factory Plugin._fromMultiPlatformYaml(
+ String name,
+ String path,
+ dynamic pluginYaml,
+ List<String> dependencies,
+ ) {
assert (pluginYaml != null && pluginYaml['platforms'] != null,
'Invalid multi-platform plugin specification.');
final YamlMap platformsYaml = pluginYaml['platforms'] as YamlMap;
@@ -118,10 +134,16 @@
name: name,
path: path,
platforms: platforms,
+ dependencies: dependencies,
);
}
- factory Plugin._fromLegacyYaml(String name, String path, dynamic pluginYaml) {
+ factory Plugin._fromLegacyYaml(
+ String name,
+ String path,
+ dynamic pluginYaml,
+ List<String> dependencies,
+ ) {
final Map<String, PluginPlatform> platforms = <String, PluginPlatform>{};
final String pluginClass = pluginYaml['pluginClass'] as String;
if (pluginYaml != null && pluginClass != null) {
@@ -147,6 +169,7 @@
name: name,
path: path,
platforms: platforms,
+ dependencies: dependencies,
);
}
@@ -232,6 +255,9 @@
final String name;
final String path;
+ /// The name of the packages this plugin depends on.
+ final List<String> dependencies;
+
/// This is a mapping from platform config key to the plugin platform spec.
final Map<String, PluginPlatform> platforms;
}
@@ -250,11 +276,13 @@
return null;
}
final String packageRootPath = fs.path.fromUri(packageRoot);
+ final YamlMap dependencies = pubspec['dependencies'];
printTrace('Found plugin $name at $packageRootPath');
return Plugin.fromYaml(
name,
packageRootPath,
flutterConfig['plugin'] as YamlMap,
+ dependencies == null ? <String>[] : <String>[...dependencies.keys],
);
}
@@ -281,29 +309,58 @@
return plugins;
}
-/// Returns true if .flutter-plugins has changed, otherwise returns false.
+/// Writes the .flutter-plugins and .flutter-plugins-dependencies files based on the list of plugins.
+/// If there aren't any plugins, then the files aren't written to disk.
+///
+/// Finally, returns [true] if .flutter-plugins or .flutter-plugins-dependencies have changed,
+/// otherwise returns [false].
bool _writeFlutterPluginsList(FlutterProject project, List<Plugin> plugins) {
+ final List<dynamic> directAppDependencies = <dynamic>[];
+ final StringBuffer flutterPluginsBuffer = StringBuffer();
+
+ final Set<String> pluginNames = <String>{};
+ for (Plugin plugin in plugins) {
+ pluginNames.add(plugin.name);
+ }
+ for (Plugin plugin in plugins) {
+ flutterPluginsBuffer.write('${plugin.name}=${escapePath(plugin.path)}\n');
+ directAppDependencies.add(<String, dynamic>{
+ 'name': plugin.name,
+ // Extract the plugin dependencies which happen to be plugins.
+ 'dependencies': <String>[...plugin.dependencies.where(pluginNames.contains)],
+ });
+ }
final File pluginsFile = project.flutterPluginsFile;
- final String oldContents = _readFlutterPluginsList(project);
- final String pluginManifest =
- plugins.map<String>((Plugin p) => '${p.name}=${escapePath(p.path)}').join('\n');
- if (pluginManifest.isNotEmpty) {
- pluginsFile.writeAsStringSync('$pluginManifest\n', flush: true);
+ final String oldPluginFileContent = _readFileContent(pluginsFile);
+ final String pluginFileContent = flutterPluginsBuffer.toString();
+ if (pluginFileContent.isNotEmpty) {
+ pluginsFile.writeAsStringSync(pluginFileContent, flush: true);
} else {
if (pluginsFile.existsSync()) {
pluginsFile.deleteSync();
}
}
- final String newContents = _readFlutterPluginsList(project);
- return oldContents != newContents;
+
+ final File dependenciesFile = project.flutterPluginsDependenciesFile;
+ final String oldDependenciesFileContent = _readFileContent(dependenciesFile);
+ final String dependenciesFileContent = json.encode(<String, dynamic>{
+ 'dependencyGraph': directAppDependencies,
+ });
+ if (pluginFileContent.isNotEmpty) {
+ dependenciesFile.writeAsStringSync(dependenciesFileContent, flush: true);
+ } else {
+ if (dependenciesFile.existsSync()) {
+ dependenciesFile.deleteSync();
+ }
+ }
+
+ return oldPluginFileContent != _readFileContent(pluginsFile)
+ || oldDependenciesFileContent != _readFileContent(dependenciesFile);
}
-/// Returns the contents of the `.flutter-plugins` file in [project], or
-/// null if that file does not exist.
-String _readFlutterPluginsList(FlutterProject project) {
- return project.flutterPluginsFile.existsSync()
- ? project.flutterPluginsFile.readAsStringSync()
- : null;
+/// Returns the contents of [File] or [null] if that file does not exist.
+String _readFileContent(File file) {
+ return file.existsSync() ? file.readAsStringSync() : null;
}
const String _androidPluginRegistryTemplateOldEmbedding = '''package io.flutter.plugins;
@@ -782,5 +839,5 @@
///
/// Assumes [refreshPluginsList] has been called since last change to `pubspec.yaml`.
bool hasPlugins(FlutterProject project) {
- return _readFlutterPluginsList(project) != null;
+ return _readFileContent(project.flutterPluginsFile) != null;
}
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 6751ab6..2da69f1 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -146,6 +146,10 @@
/// The `.flutter-plugins` file of this project.
File get flutterPluginsFile => directory.childFile('.flutter-plugins');
+ /// The `.flutter-plugins-dependencies` file of this project,
+ /// which contains the dependencies each plugin depends on.
+ File get flutterPluginsDependenciesFile => directory.childFile('.flutter-plugins-dependencies');
+
/// The `.dart-tool` directory of this project.
Directory get dartTool => directory.childDirectory('.dart_tool');
diff --git a/packages/flutter_tools/templates/app/.gitignore.tmpl b/packages/flutter_tools/templates/app/.gitignore.tmpl
index 437cb45..ae1f183 100644
--- a/packages/flutter_tools/templates/app/.gitignore.tmpl
+++ b/packages/flutter_tools/templates/app/.gitignore.tmpl
@@ -24,6 +24,7 @@
**/doc/api/
.dart_tool/
.flutter-plugins
+.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
diff --git a/packages/flutter_tools/templates/module/common/.gitignore.tmpl b/packages/flutter_tools/templates/module/common/.gitignore.tmpl
index cdecf14..86f4691 100644
--- a/packages/flutter_tools/templates/module/common/.gitignore.tmpl
+++ b/packages/flutter_tools/templates/module/common/.gitignore.tmpl
@@ -39,3 +39,4 @@
.android/
.ios/
.flutter-plugins
+.flutter-plugins-dependencies
diff --git a/packages/flutter_tools/templates/package/.gitignore.tmpl b/packages/flutter_tools/templates/package/.gitignore.tmpl
index 6ffedae..bb431f0 100644
--- a/packages/flutter_tools/templates/package/.gitignore.tmpl
+++ b/packages/flutter_tools/templates/package/.gitignore.tmpl
@@ -24,6 +24,7 @@
**/doc/api/
.dart_tool/
.flutter-plugins
+.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
diff --git a/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart b/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart
index ea91679..8f96ebb 100644
--- a/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart
+++ b/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart
@@ -20,7 +20,7 @@
final dynamic pluginYaml = loadYaml(pluginYamlRaw);
final Plugin plugin =
- Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml);
+ Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml, const <String>[]);
final AndroidPlugin androidPlugin =
plugin.platforms[AndroidPlugin.kConfigKey];
@@ -53,7 +53,7 @@
final dynamic pluginYaml = loadYaml(pluginYamlRaw);
final Plugin plugin =
- Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml);
+ Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml, const <String>[]);
final AndroidPlugin androidPlugin =
plugin.platforms[AndroidPlugin.kConfigKey];
@@ -100,7 +100,7 @@
final dynamic pluginYaml = loadYaml(pluginYamlRaw);
final Plugin plugin =
- Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml);
+ Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml, const <String>[]);
final AndroidPlugin androidPlugin =
plugin.platforms[AndroidPlugin.kConfigKey];
@@ -144,7 +144,7 @@
final dynamic pluginYaml = loadYaml(pluginYamlRaw);
final Plugin plugin =
- Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml);
+ Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml, const <String>[]);
expect(plugin.platforms, <String, PluginPlatform> {});
});
diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart
index 802d8034..5d35408 100644
--- a/packages/flutter_tools/test/general.shard/plugins_test.dart
+++ b/packages/flutter_tools/test/general.shard/plugins_test.dart
@@ -9,7 +9,7 @@
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/plugins.dart';
import 'package:flutter_tools/src/project.dart';
-
+import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
import '../src/common.dart';
@@ -32,7 +32,8 @@
// Add basic properties to the Flutter project and subprojects
flutterProject = MockFlutterProject();
when(flutterProject.directory).thenReturn(fs.directory('/'));
- when(flutterProject.flutterPluginsFile).thenReturn(flutterProject.directory.childFile('.plugins'));
+ when(flutterProject.flutterPluginsFile).thenReturn(flutterProject.directory.childFile('.flutter-plugins'));
+ when(flutterProject.flutterPluginsDependenciesFile).thenReturn(flutterProject.directory.childFile('.flutter-plugins-dependencies'));
iosProject = MockIosProject();
when(flutterProject.ios).thenReturn(iosProject);
when(iosProject.pluginRegistrantHost).thenReturn(flutterProject.directory.childDirectory('Runner'));
@@ -189,6 +190,37 @@
);
}
+ void createPluginWithDependencies({
+ @required String name,
+ @required List<String> dependencies,
+ }) {
+ assert(name != null);
+ assert(dependencies != null);
+
+ final Directory pluginDirectory = fs.systemTempDirectory.createTempSync('plugin.');
+ pluginDirectory
+ .childFile('pubspec.yaml')
+ .writeAsStringSync('''
+name: $name
+flutter:
+ plugin:
+ androidPackage: plugin2
+ pluginClass: UseNewEmbedding
+dependencies:
+''');
+ for (String dependency in dependencies) {
+ pluginDirectory
+ .childFile('pubspec.yaml')
+ .writeAsStringSync(' $dependency:\n', mode: FileMode.append);
+ }
+ flutterProject.directory
+ .childFile('.packages')
+ .writeAsStringSync(
+ '$name:${pluginDirectory.childDirectory('lib').uri.toString()}\n',
+ mode: FileMode.append,
+ );
+ }
+
// Creates the files that would indicate that pod install has run for the
// given project.
void simulatePodInstallRun(XcodeBasedProject project) {
@@ -199,6 +231,7 @@
testUsingContext('Refreshing the plugin list is a no-op when the plugins list stays empty', () {
refreshPluginsList(flutterProject);
expect(flutterProject.flutterPluginsFile.existsSync(), false);
+ expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), false);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
@@ -210,6 +243,7 @@
when(macosProject.existsSync()).thenReturn(false);
refreshPluginsList(flutterProject);
expect(flutterProject.flutterPluginsFile.existsSync(), false);
+ expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), false);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
@@ -221,6 +255,47 @@
when(macosProject.existsSync()).thenReturn(false);
refreshPluginsList(flutterProject);
expect(flutterProject.flutterPluginsFile.existsSync(), true);
+ expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Refreshing the plugin list modifies .flutter-plugins and .flutter-plugins-dependencies when there are plugins', () {
+ createPluginWithDependencies(name: 'plugin-a', dependencies: const <String>['plugin-b', 'plugin-c', 'random-package']);
+ createPluginWithDependencies(name: 'plugin-b', dependencies: const <String>['plugin-c']);
+ createPluginWithDependencies(name: 'plugin-c', dependencies: const <String>[]);
+ when(iosProject.existsSync()).thenReturn(false);
+ when(macosProject.existsSync()).thenReturn(false);
+
+ refreshPluginsList(flutterProject);
+
+ expect(flutterProject.flutterPluginsFile.existsSync(), true);
+ expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
+ expect(flutterProject.flutterPluginsFile.readAsStringSync(),
+ 'plugin-a=/.tmp_rand0/plugin.rand0/\n'
+ 'plugin-b=/.tmp_rand0/plugin.rand1/\n'
+ 'plugin-c=/.tmp_rand0/plugin.rand2/\n'
+ ''
+ );
+ expect(flutterProject.flutterPluginsDependenciesFile.readAsStringSync(),
+ '{'
+ '"dependencyGraph":['
+ '{'
+ '"name":"plugin-a",'
+ '"dependencies":["plugin-b","plugin-c"]'
+ '},'
+ '{'
+ '"name":"plugin-b",'
+ '"dependencies":["plugin-c"]'
+ '},'
+ '{'
+ '"name":"plugin-c",'
+ '"dependencies":[]'
+ '}'
+ ']'
+ '}'
+ );
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),