[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
This commit is contained in:
stuartmorgan
2021-12-07 10:02:24 -05:00
committed by GitHub
parent 52f49c1aa2
commit 3de67cea56
4 changed files with 139 additions and 27 deletions

View File

@ -5,6 +5,11 @@
dependencies to path-based dependencies. dependencies to path-based dependencies.
- Adds a (hidden) `--run-on-dirty-packages` flag for use with - Adds a (hidden) `--run-on-dirty-packages` flag for use with
`make-deps-path-based` in CI. `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 - Fix `federation-safety-check` handling of plugin deletion, and of top-level
files in unfederated plugins whose names match federated plugin heuristics files in unfederated plugins whose names match federated plugin heuristics
(e.g., `packages/foo/foo_android.iml`). (e.g., `packages/foo/foo_android.iml`).

View File

@ -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 All examples assume running from source; see above for running the
published version instead. published version instead.
Note that the `plugins` argument, despite the name, applies to any package. Most commands take a `--packages` argument to control which package(s) the
(It will likely be renamed `packages` in the future.) 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 ### Format Code

View File

@ -339,7 +339,7 @@ abstract class PluginCommand extends Command<void> {
final List<String> changedFiles = final List<String> changedFiles =
await gitVersionFinder.getChangedFiles(); await gitVersionFinder.getChangedFiles();
if (!_changesRequireFullTest(changedFiles)) { if (!_changesRequireFullTest(changedFiles)) {
packages = _getChangedPackages(changedFiles); packages = _getChangedPackageNames(changedFiles);
} }
} else if (getBoolArg(_runOnDirtyPackagesArg)) { } else if (getBoolArg(_runOnDirtyPackagesArg)) {
final GitVersionFinder gitVersionFinder = final GitVersionFinder gitVersionFinder =
@ -348,7 +348,7 @@ abstract class PluginCommand extends Command<void> {
// _changesRequireFullTest is deliberately not used here, as this flag is // _changesRequireFullTest is deliberately not used here, as this flag is
// intended for use in CI to re-test packages changed by // intended for use in CI to re-test packages changed by
// 'make-deps-path-based'. // 'make-deps-path-based'.
packages = _getChangedPackages( packages = _getChangedPackageNames(
await gitVersionFinder.getChangedFiles(includeUncommitted: true)); await gitVersionFinder.getChangedFiles(includeUncommitted: true));
// For the same reason, empty is not treated as "all packages" as it is // For the same reason, empty is not treated as "all packages" as it is
// for other flags. // for other flags.
@ -379,21 +379,23 @@ abstract class PluginCommand extends Command<void> {
await for (final FileSystemEntity subdir await for (final FileSystemEntity subdir
in entity.list(followLinks: false)) { in entity.list(followLinks: false)) {
if (_isDartPackage(subdir)) { if (_isDartPackage(subdir)) {
// If --plugin=my_plugin is passed, then match all federated // There are three ways for a federated plugin to match:
// plugins under 'my_plugin'. Also match if the exact plugin is // - package name (path_provider_android)
// passed. // - fully specified name (path_provider/path_provider_android)
final String relativePath = // - group name (path_provider), which matches all packages in
path.relative(subdir.path, from: dir.path); // the group
final String packageName = path.basename(subdir.path); final Set<String> possibleMatches = <String>{
final String basenamePath = path.basename(entity.path); path.basename(subdir.path), // package name
path.basename(entity.path), // group name
path.relative(subdir.path, from: dir.path), // fully specified
};
if (packages.isEmpty || if (packages.isEmpty ||
packages.contains(relativePath) || packages.intersection(possibleMatches).isNotEmpty) {
packages.contains(basenamePath)) {
yield PackageEnumerationEntry( yield PackageEnumerationEntry(
RepositoryPackage(subdir as Directory), RepositoryPackage(subdir as Directory),
excluded: excludedPluginNames.contains(basenamePath) || excluded: excludedPluginNames
excludedPluginNames.contains(packageName) || .intersection(possibleMatches)
excludedPluginNames.contains(relativePath)); .isNotEmpty);
} }
} }
} }
@ -454,17 +456,48 @@ abstract class PluginCommand extends Command<void> {
return gitVersionFinder; 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). // 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>{}; 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) { for (final String path in changedFiles) {
final List<String> pathComponents = p.posix.split(path); final List<String> pathComponents = p.posix.split(path);
final int packagesIndex = final int packagesIndex =
pathComponents.indexWhere((String element) => element == 'packages'); pathComponents.indexWhere((String element) => element == 'packages');
if (packagesIndex != -1) { 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) { if (packages.isEmpty) {

View File

@ -180,6 +180,79 @@ void main() {
expect(command.plugins, unorderedEquals(<String>[])); 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', () { group('conflicting package selection', () {
test('does not allow --packages with --run-on-changed-packages', test('does not allow --packages with --run-on-changed-packages',
() async { () async {
@ -442,7 +515,7 @@ packages/plugin1/plugin1_web/plugin1_web.dart
}); });
test( 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 { () async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[ processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: ''' MockProcess(stdout: '''
@ -451,17 +524,13 @@ packages/plugin1/plugin1/plugin1.dart
]; ];
final Directory plugin1 = final Directory plugin1 =
createFakePlugin('plugin1', packagesDir.childDirectory('plugin1')); createFakePlugin('plugin1', packagesDir.childDirectory('plugin1'));
final Directory plugin2 = createFakePlugin('plugin1_platform_interface', createFakePlugin('plugin1_platform_interface',
packagesDir.childDirectory('plugin1')); packagesDir.childDirectory('plugin1'));
final Directory plugin3 = createFakePlugin( createFakePlugin('plugin1_web', packagesDir.childDirectory('plugin1'));
'plugin1_web', packagesDir.childDirectory('plugin1'));
await runCapturingPrint(runner, await runCapturingPrint(runner,
<String>['sample', '--base-sha=main', '--run-on-changed-packages']); <String>['sample', '--base-sha=main', '--run-on-changed-packages']);
expect( expect(command.plugins, unorderedEquals(<String>[plugin1.path]));
command.plugins,
unorderedEquals(
<String>[plugin1.path, plugin2.path, plugin3.path]));
}); });
test('--exclude flag works with --run-on-changed-packages', () async { test('--exclude flag works with --run-on-changed-packages', () async {