From 3de67cea560a4ca7082f56b1033db1af53845245 Mon Sep 17 00:00:00 2001 From: stuartmorgan <stuartmorgan@google.com> Date: Tue, 7 Dec 2021 10:02:24 -0500 Subject: [PATCH] [flutter_plugin_tools] Improve package targeting (#4577) Improve package targeting: - `--run-on-changed-packages` now includes only changed packages in a federated plugin, not all packages. Include all packages isn't useful since (without changes that would cause them to be included anyway) package dependencies are not path-based between packages. - `--packages` now allows specifying package names of federated plugins. E.g., `--packages=path_provider_ios` will now work, instead of requiring `--packages=path_provider/path_provider_ios`. The fully qualified form still works as well (and is still needed for app-facing packages to disambiguate from the plugin group). Fixes https://github.com/flutter/flutter/issues/94618 --- script/tool/CHANGELOG.md | 5 ++ script/tool/README.md | 9 +- .../tool/lib/src/common/plugin_command.dart | 67 +++++++++++---- .../tool/test/common/plugin_command_test.dart | 85 +++++++++++++++++-- 4 files changed, 139 insertions(+), 27 deletions(-) diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md index dec218e2ba..72539c24c5 100644 --- a/script/tool/CHANGELOG.md +++ b/script/tool/CHANGELOG.md @@ -5,6 +5,11 @@ dependencies to path-based dependencies. - Adds a (hidden) `--run-on-dirty-packages` flag for use with `make-deps-path-based` in CI. +- `--packages` now allows using a federated plugin's package as a target without + fully specifying it (if it is not the same as the plugin's name). E.g., + `--packages=path_provide_ios` now works. +- `--run-on-changed-packages` now includes only the changed packages in a + federated plugin, not all packages in that plugin. - Fix `federation-safety-check` handling of plugin deletion, and of top-level files in unfederated plugins whose names match federated plugin heuristics (e.g., `packages/foo/foo_android.iml`). diff --git a/script/tool/README.md b/script/tool/README.md index 1a87f09875..613cb5456e 100644 --- a/script/tool/README.md +++ b/script/tool/README.md @@ -51,8 +51,13 @@ following shows a number of common commands being run for a specific plugin. All examples assume running from source; see above for running the published version instead. -Note that the `plugins` argument, despite the name, applies to any package. -(It will likely be renamed `packages` in the future.) +Most commands take a `--packages` argument to control which package(s) the +command is targetting. An package name can be any of: +- The name of a package (e.g., `path_provider_android`). +- The name of a federated plugin (e.g., `path_provider`), in which case all + packages that make up that plugin will be targetted. +- A combination federated_plugin_name/package_name (e.g., + `path_provider/path_provider` for the app-facing package). ### Format Code diff --git a/script/tool/lib/src/common/plugin_command.dart b/script/tool/lib/src/common/plugin_command.dart index 7166c754e1..1846635682 100644 --- a/script/tool/lib/src/common/plugin_command.dart +++ b/script/tool/lib/src/common/plugin_command.dart @@ -339,7 +339,7 @@ abstract class PluginCommand extends Command<void> { final List<String> changedFiles = await gitVersionFinder.getChangedFiles(); if (!_changesRequireFullTest(changedFiles)) { - packages = _getChangedPackages(changedFiles); + packages = _getChangedPackageNames(changedFiles); } } else if (getBoolArg(_runOnDirtyPackagesArg)) { final GitVersionFinder gitVersionFinder = @@ -348,7 +348,7 @@ abstract class PluginCommand extends Command<void> { // _changesRequireFullTest is deliberately not used here, as this flag is // intended for use in CI to re-test packages changed by // 'make-deps-path-based'. - packages = _getChangedPackages( + packages = _getChangedPackageNames( await gitVersionFinder.getChangedFiles(includeUncommitted: true)); // For the same reason, empty is not treated as "all packages" as it is // for other flags. @@ -379,21 +379,23 @@ abstract class PluginCommand extends Command<void> { await for (final FileSystemEntity subdir in entity.list(followLinks: false)) { if (_isDartPackage(subdir)) { - // If --plugin=my_plugin is passed, then match all federated - // plugins under 'my_plugin'. Also match if the exact plugin is - // passed. - final String relativePath = - path.relative(subdir.path, from: dir.path); - final String packageName = path.basename(subdir.path); - final String basenamePath = path.basename(entity.path); + // There are three ways for a federated plugin to match: + // - package name (path_provider_android) + // - fully specified name (path_provider/path_provider_android) + // - group name (path_provider), which matches all packages in + // the group + final Set<String> possibleMatches = <String>{ + path.basename(subdir.path), // package name + path.basename(entity.path), // group name + path.relative(subdir.path, from: dir.path), // fully specified + }; if (packages.isEmpty || - packages.contains(relativePath) || - packages.contains(basenamePath)) { + packages.intersection(possibleMatches).isNotEmpty) { yield PackageEnumerationEntry( RepositoryPackage(subdir as Directory), - excluded: excludedPluginNames.contains(basenamePath) || - excludedPluginNames.contains(packageName) || - excludedPluginNames.contains(relativePath)); + excluded: excludedPluginNames + .intersection(possibleMatches) + .isNotEmpty); } } } @@ -454,17 +456,48 @@ abstract class PluginCommand extends Command<void> { return gitVersionFinder; } - // Returns packages that have been changed given a list of changed files. + // Returns the names of packages that have been changed given a list of + // changed files. + // + // The names will either be the actual package names, or potentially + // group/name specifiers (for example, path_provider/path_provider) for + // packages in federated plugins. // // The paths must use POSIX separators (e.g., as provided by git output). - Set<String> _getChangedPackages(List<String> changedFiles) { + Set<String> _getChangedPackageNames(List<String> changedFiles) { final Set<String> packages = <String>{}; + + // A helper function that returns true if candidatePackageName looks like an + // implementation package of a plugin called pluginName. Used to determine + // if .../packages/parentName/candidatePackageName/... + // looks like a path in a federated plugin package (candidatePackageName) + // rather than a top-level package (parentName). + bool isFederatedPackage(String candidatePackageName, String parentName) { + return candidatePackageName == parentName || + candidatePackageName.startsWith('${parentName}_'); + } + for (final String path in changedFiles) { final List<String> pathComponents = p.posix.split(path); final int packagesIndex = pathComponents.indexWhere((String element) => element == 'packages'); if (packagesIndex != -1) { - packages.add(pathComponents[packagesIndex + 1]); + // Find the name of the directory directly under packages. This is + // either the name of the package, or a plugin group directory for + // a federated plugin. + final String topLevelName = pathComponents[packagesIndex + 1]; + String packageName = topLevelName; + if (packagesIndex + 2 < pathComponents.length && + isFederatedPackage( + pathComponents[packagesIndex + 2], topLevelName)) { + // This looks like a federated package; use the full specifier if + // the name would be ambiguous (i.e., for the app-facing package). + packageName = pathComponents[packagesIndex + 2]; + if (packageName == topLevelName) { + packageName = '$topLevelName/$packageName'; + } + } + packages.add(packageName); } } if (packages.isEmpty) { diff --git a/script/tool/test/common/plugin_command_test.dart b/script/tool/test/common/plugin_command_test.dart index 222df544f3..28a03c61d5 100644 --- a/script/tool/test/common/plugin_command_test.dart +++ b/script/tool/test/common/plugin_command_test.dart @@ -180,6 +180,79 @@ void main() { expect(command.plugins, unorderedEquals(<String>[])); }); + test( + 'explicitly specifying the plugin (group) name of a federated plugin ' + 'should include all plugins in the group', () async { + processRunner.mockProcessesForExecutable['git-diff'] = <Process>[ + MockProcess(stdout: ''' +packages/plugin1/plugin1/plugin1.dart +'''), + ]; + final Directory pluginGroup = packagesDir.childDirectory('plugin1'); + final Directory appFacingPackage = + createFakePlugin('plugin1', pluginGroup); + final Directory platformInterfacePackage = + createFakePlugin('plugin1_platform_interface', pluginGroup); + final Directory implementationPackage = + createFakePlugin('plugin1_web', pluginGroup); + + await runCapturingPrint( + runner, <String>['sample', '--base-sha=main', '--packages=plugin1']); + + expect( + command.plugins, + unorderedEquals(<String>[ + appFacingPackage.path, + platformInterfacePackage.path, + implementationPackage.path + ])); + }); + + test( + 'specifying the app-facing package of a federated plugin using its ' + 'fully qualified name should include only that package', () async { + processRunner.mockProcessesForExecutable['git-diff'] = <Process>[ + MockProcess(stdout: ''' +packages/plugin1/plugin1/plugin1.dart +'''), + ]; + final Directory pluginGroup = packagesDir.childDirectory('plugin1'); + final Directory appFacingPackage = + createFakePlugin('plugin1', pluginGroup); + createFakePlugin('plugin1_platform_interface', pluginGroup); + createFakePlugin('plugin1_web', pluginGroup); + + await runCapturingPrint(runner, + <String>['sample', '--base-sha=main', '--packages=plugin1/plugin1']); + + expect(command.plugins, unorderedEquals(<String>[appFacingPackage.path])); + }); + + test( + 'specifying a package of a federated plugin by its name should ' + 'include only that package', () async { + processRunner.mockProcessesForExecutable['git-diff'] = <Process>[ + MockProcess(stdout: ''' +packages/plugin1/plugin1/plugin1.dart +'''), + ]; + final Directory pluginGroup = packagesDir.childDirectory('plugin1'); + + createFakePlugin('plugin1', pluginGroup); + final Directory platformInterfacePackage = + createFakePlugin('plugin1_platform_interface', pluginGroup); + createFakePlugin('plugin1_web', pluginGroup); + + await runCapturingPrint(runner, <String>[ + 'sample', + '--base-sha=main', + '--packages=plugin1_platform_interface' + ]); + + expect(command.plugins, + unorderedEquals(<String>[platformInterfacePackage.path])); + }); + group('conflicting package selection', () { test('does not allow --packages with --run-on-changed-packages', () async { @@ -442,7 +515,7 @@ packages/plugin1/plugin1_web/plugin1_web.dart }); test( - 'changing one plugin in a federated group should include all plugins in the group', + 'changing one plugin in a federated group should only include that plugin', () async { processRunner.mockProcessesForExecutable['git-diff'] = <Process>[ MockProcess(stdout: ''' @@ -451,17 +524,13 @@ packages/plugin1/plugin1/plugin1.dart ]; final Directory plugin1 = createFakePlugin('plugin1', packagesDir.childDirectory('plugin1')); - final Directory plugin2 = createFakePlugin('plugin1_platform_interface', + createFakePlugin('plugin1_platform_interface', packagesDir.childDirectory('plugin1')); - final Directory plugin3 = createFakePlugin( - 'plugin1_web', packagesDir.childDirectory('plugin1')); + createFakePlugin('plugin1_web', packagesDir.childDirectory('plugin1')); await runCapturingPrint(runner, <String>['sample', '--base-sha=main', '--run-on-changed-packages']); - expect( - command.plugins, - unorderedEquals( - <String>[plugin1.path, plugin2.path, plugin3.path])); + expect(command.plugins, unorderedEquals(<String>[plugin1.path])); }); test('--exclude flag works with --run-on-changed-packages', () async {