diff --git a/script/tool/lib/src/analyze_command.dart b/script/tool/lib/src/analyze_command.dart index 2b728e2b9073..faad7f4736eb 100644 --- a/script/tool/lib/src/analyze_command.dart +++ b/script/tool/lib/src/analyze_command.dart @@ -5,13 +5,14 @@ import 'dart:async'; import 'package:file/file.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_command.dart'; import 'package:platform/platform.dart'; import 'package:yaml/yaml.dart'; import 'common/core.dart'; import 'common/package_looping_command.dart'; +import 'common/plugin_command.dart'; import 'common/process_runner.dart'; +import 'common/repository_package.dart'; const int _exitPackagesGetFailed = 3; @@ -55,8 +56,9 @@ class AnalyzeCommand extends PackageLoopingCommand { final bool hasLongOutput = false; /// Checks that there are no unexpected analysis_options.yaml files. - bool _hasUnexpecetdAnalysisOptions(Directory package) { - final List files = package.listSync(recursive: true); + bool _hasUnexpecetdAnalysisOptions(RepositoryPackage package) { + final List files = + package.directory.listSync(recursive: true); for (final FileSystemEntity file in files) { if (file.basename != 'analysis_options.yaml' && file.basename != '.analysis_options') { @@ -87,7 +89,7 @@ class AnalyzeCommand extends PackageLoopingCommand { Future _runPackagesGetOnTargetPackages() async { final List packageDirectories = await getTargetPackagesAndSubpackages() - .map((PackageEnumerationEntry package) => package.directory) + .map((PackageEnumerationEntry entry) => entry.package.directory) .toList(); final Set packagePaths = packageDirectories.map((Directory dir) => dir.path).toSet(); @@ -135,13 +137,13 @@ class AnalyzeCommand extends PackageLoopingCommand { } @override - Future runForPackage(Directory package) async { + Future runForPackage(RepositoryPackage package) async { if (_hasUnexpecetdAnalysisOptions(package)) { return PackageResult.fail(['Unexpected local analysis options']); } final int exitCode = await processRunner.runAndStream( _dartBinaryPath, ['analyze', '--fatal-infos'], - workingDir: package); + workingDir: package.directory); if (exitCode != 0) { return PackageResult.fail(); } diff --git a/script/tool/lib/src/build_examples_command.dart b/script/tool/lib/src/build_examples_command.dart index 0cac09980c94..ac5e84b7c3c7 100644 --- a/script/tool/lib/src/build_examples_command.dart +++ b/script/tool/lib/src/build_examples_command.dart @@ -11,6 +11,7 @@ import 'common/core.dart'; import 'common/package_looping_command.dart'; import 'common/plugin_utils.dart'; import 'common/process_runner.dart'; +import 'common/repository_package.dart'; /// Key for APK. const String _platformFlagApk = 'apk'; @@ -96,7 +97,7 @@ class BuildExamplesCommand extends PackageLoopingCommand { } @override - Future runForPackage(Directory package) async { + Future runForPackage(RepositoryPackage package) async { final List errors = []; final Iterable<_PlatformDetails> requestedPlatforms = _platforms.entries @@ -126,9 +127,9 @@ class BuildExamplesCommand extends PackageLoopingCommand { } print(''); - for (final Directory example in getExamplesForPlugin(package)) { + for (final RepositoryPackage example in package.getExamples()) { final String packageName = - getRelativePosixPath(example, from: packagesDir); + getRelativePosixPath(example.directory, from: packagesDir); for (final _PlatformDetails platform in buildPlatforms) { String buildPlatform = platform.label; @@ -149,7 +150,7 @@ class BuildExamplesCommand extends PackageLoopingCommand { } Future _buildExample( - Directory example, + RepositoryPackage example, String flutterBuildType, { List extraBuildFlags = const [], }) async { @@ -164,7 +165,7 @@ class BuildExamplesCommand extends PackageLoopingCommand { if (enableExperiment.isNotEmpty) '--enable-experiment=$enableExperiment', ], - workingDir: example, + workingDir: example.directory, ); return exitCode == 0; } diff --git a/script/tool/lib/src/common/package_looping_command.dart b/script/tool/lib/src/common/package_looping_command.dart index 0e0976ecc6a7..00caeb30ef42 100644 --- a/script/tool/lib/src/common/package_looping_command.dart +++ b/script/tool/lib/src/common/package_looping_command.dart @@ -13,6 +13,7 @@ import 'package:platform/platform.dart'; import 'core.dart'; import 'plugin_command.dart'; import 'process_runner.dart'; +import 'repository_package.dart'; /// Possible outcomes of a command run for a package. enum RunState { @@ -84,7 +85,7 @@ abstract class PackageLoopingCommand extends PluginCommand { int _otherWarningCount = 0; /// The package currently being run by [runForPackage]. - PackageEnumerationEntry? _currentPackage; + PackageEnumerationEntry? _currentPackageEntry; /// Called during [run] before any calls to [runForPackage]. This provides an /// opportunity to fail early if the command can't be run (e.g., because the @@ -97,7 +98,7 @@ abstract class PackageLoopingCommand extends PluginCommand { /// be included in the final error summary (e.g., a command that only has a /// single failure mode), or strings that should be listed for that package /// in the final summary. An empty list indicates success. - Future runForPackage(Directory package); + Future runForPackage(RepositoryPackage package); /// Called during [run] after all calls to [runForPackage]. This provides an /// opportunity to do any cleanup of run-level state. @@ -155,31 +156,13 @@ abstract class PackageLoopingCommand extends PluginCommand { /// things that might be useful to someone debugging an unexpected result. void logWarning(String warningMessage) { print(Colorize(warningMessage)..yellow()); - if (_currentPackage != null) { - _packagesWithWarnings.add(_currentPackage!); + if (_currentPackageEntry != null) { + _packagesWithWarnings.add(_currentPackageEntry!); } else { ++_otherWarningCount; } } - /// Returns the identifying name to use for [package]. - /// - /// Implementations should not expect a specific format for this string, since - /// it uses heuristics to try to be precise without being overly verbose. If - /// an exact format (e.g., published name, or basename) is required, that - /// should be used instead. - String getPackageDescription(Directory package) { - String packageName = getRelativePosixPath(package, from: packagesDir); - final List components = p.posix.split(packageName); - // For the common federated plugin pattern of `foo/foo_subpackage`, drop - // the first part since it's not useful. - if (components.length >= 2 && - components[1].startsWith('${components[0]}_')) { - packageName = p.posix.joinAll(components.sublist(1)); - } - return packageName; - } - /// Returns the relative path from [from] to [entity] in Posix style. /// /// This should be used when, for example, printing package-relative paths in @@ -219,36 +202,36 @@ abstract class PackageLoopingCommand extends PluginCommand { Future _runInternal() async { _packagesWithWarnings.clear(); _otherWarningCount = 0; - _currentPackage = null; + _currentPackageEntry = null; await initializeRun(); - final List packages = includeSubpackages + final List targetPackages = includeSubpackages ? await getTargetPackagesAndSubpackages(filterExcluded: false).toList() : await getTargetPackages(filterExcluded: false).toList(); final Map results = {}; - for (final PackageEnumerationEntry package in packages) { - _currentPackage = package; - _printPackageHeading(package); + for (final PackageEnumerationEntry entry in targetPackages) { + _currentPackageEntry = entry; + _printPackageHeading(entry); // Command implementations should never see excluded packages; they are // included at this level only for logging. - if (package.excluded) { - results[package] = PackageResult.exclude(); + if (entry.excluded) { + results[entry] = PackageResult.exclude(); continue; } - final PackageResult result = await runForPackage(package.directory); + final PackageResult result = await runForPackage(entry.package); if (result.state == RunState.skipped) { final String message = '${indentation}SKIPPING: ${result.details.first}'; captureOutput ? print(message) : print(Colorize(message)..darkGray()); } - results[package] = result; + results[entry] = result; } - _currentPackage = null; + _currentPackageEntry = null; completeRun(); @@ -256,13 +239,13 @@ abstract class PackageLoopingCommand extends PluginCommand { // If there were any errors reported, summarize them and exit. if (results.values .any((PackageResult result) => result.state == RunState.failed)) { - _printFailureSummary(packages, results); + _printFailureSummary(targetPackages, results); return false; } // Otherwise, print a summary of what ran for ease of auditing that all the // expected tests ran. - _printRunSummary(packages, results); + _printRunSummary(targetPackages, results); print('\n'); _printSuccess('No issues found!'); @@ -283,9 +266,9 @@ abstract class PackageLoopingCommand extends PluginCommand { /// Something is always printed to make it easier to distinguish between /// a command running for a package and producing no output, and a command /// not having been run for a package. - void _printPackageHeading(PackageEnumerationEntry package) { - final String packageDisplayName = getPackageDescription(package.directory); - String heading = package.excluded + void _printPackageHeading(PackageEnumerationEntry entry) { + final String packageDisplayName = entry.package.displayName; + String heading = entry.excluded ? 'Not running for $packageDisplayName; excluded' : 'Running for $packageDisplayName'; if (hasLongOutput) { @@ -295,16 +278,15 @@ abstract class PackageLoopingCommand extends PluginCommand { || $heading ============================================================ '''; - } else if (!package.excluded) { + } else if (!entry.excluded) { heading = '$heading...'; } if (captureOutput) { print(heading); } else { final Colorize colorizeHeading = Colorize(heading); - print(package.excluded - ? colorizeHeading.darkGray() - : colorizeHeading.cyan()); + print( + entry.excluded ? colorizeHeading.darkGray() : colorizeHeading.cyan()); } } @@ -349,17 +331,18 @@ abstract class PackageLoopingCommand extends PluginCommand { /// Prints a one-line-per-package overview of the run results for each /// package. - void _printPerPackageRunOverview(List packages, + void _printPerPackageRunOverview( + List packageEnumeration, {required Set skipped}) { print('Run overview:'); - for (final PackageEnumerationEntry package in packages) { - final bool hadWarning = _packagesWithWarnings.contains(package); + for (final PackageEnumerationEntry entry in packageEnumeration) { + final bool hadWarning = _packagesWithWarnings.contains(entry); Styles style; String summary; - if (package.excluded) { + if (entry.excluded) { summary = 'excluded'; style = Styles.DARK_GRAY; - } else if (skipped.contains(package)) { + } else if (skipped.contains(entry)) { summary = 'skipped'; style = hadWarning ? Styles.LIGHT_YELLOW : Styles.DARK_GRAY; } else { @@ -373,18 +356,18 @@ abstract class PackageLoopingCommand extends PluginCommand { if (!captureOutput) { summary = (Colorize(summary)..apply(style)).toString(); } - print(' ${getPackageDescription(package.directory)} - $summary'); + print(' ${entry.package.displayName} - $summary'); } print(''); } /// Prints a summary of all of the failures from [results]. - void _printFailureSummary(List packages, + void _printFailureSummary(List packageEnumeration, Map results) { const String indentation = ' '; _printError(failureListHeader); - for (final PackageEnumerationEntry package in packages) { - final PackageResult result = results[package]!; + for (final PackageEnumerationEntry entry in packageEnumeration) { + final PackageResult result = results[entry]!; if (result.state == RunState.failed) { final String errorIndentation = indentation * 2; String errorDetails = ''; @@ -392,8 +375,7 @@ abstract class PackageLoopingCommand extends PluginCommand { errorDetails = ':\n$errorIndentation${result.details.join('\n$errorIndentation')}'; } - _printError( - '$indentation${getPackageDescription(package.directory)}$errorDetails'); + _printError('$indentation${entry.package.displayName}$errorDetails'); } } _printError(failureListFooter); diff --git a/script/tool/lib/src/common/plugin_command.dart b/script/tool/lib/src/common/plugin_command.dart index 10f423360878..ec51261ab617 100644 --- a/script/tool/lib/src/common/plugin_command.dart +++ b/script/tool/lib/src/common/plugin_command.dart @@ -14,15 +14,18 @@ import 'package:yaml/yaml.dart'; import 'core.dart'; import 'git_version_finder.dart'; import 'process_runner.dart'; +import 'repository_package.dart'; /// An entry in package enumeration for APIs that need to include extra /// data about the entry. class PackageEnumerationEntry { - /// Creates a new entry for the given package directory. - PackageEnumerationEntry(this.directory, {required this.excluded}); + /// Creates a new entry for the given package. + PackageEnumerationEntry(this.package, {required this.excluded}); - /// The package's location. - final Directory directory; + /// The package this entry corresponds to. Be sure to check `excluded` before + /// using this, as having an entry does not necessarily mean that the package + /// should be included in the processing of the enumeration. + final RepositoryPackage package; /// Whether or not this package was excluded by the command invocation. final bool excluded; @@ -225,7 +228,7 @@ abstract class PluginCommand extends Command { final List allPlugins = await _getAllPackages().toList(); allPlugins.sort((PackageEnumerationEntry p1, PackageEnumerationEntry p2) => - p1.directory.path.compareTo(p2.directory.path)); + p1.package.path.compareTo(p2.package.path)); final int shardSize = allPlugins.length ~/ shardCount + (allPlugins.length % shardCount == 0 ? 0 : 1); final int start = min(shardIndex * shardSize, allPlugins.length); @@ -287,7 +290,8 @@ abstract class PluginCommand extends Command { // A top-level Dart package is a plugin package. if (_isDartPackage(entity)) { if (plugins.isEmpty || plugins.contains(p.basename(entity.path))) { - yield PackageEnumerationEntry(entity as Directory, + yield PackageEnumerationEntry( + RepositoryPackage(entity as Directory), excluded: excludedPluginNames.contains(entity.basename)); } } else if (entity is Directory) { @@ -305,7 +309,8 @@ abstract class PluginCommand extends Command { if (plugins.isEmpty || plugins.contains(relativePath) || plugins.contains(basenamePath)) { - yield PackageEnumerationEntry(subdir as Directory, + yield PackageEnumerationEntry( + RepositoryPackage(subdir as Directory), excluded: excludedPluginNames.contains(basenamePath) || excludedPluginNames.contains(packageName) || excludedPluginNames.contains(relativePath)); @@ -327,26 +332,26 @@ abstract class PluginCommand extends Command { await for (final PackageEnumerationEntry plugin in getTargetPackages(filterExcluded: filterExcluded)) { yield plugin; - yield* plugin.directory + yield* plugin.package.directory .list(recursive: true, followLinks: false) .where(_isDartPackage) .map((FileSystemEntity directory) => PackageEnumerationEntry( - directory as Directory, // _isDartPackage guarantees this works. + // _isDartPackage guarantees that this cast is valid. + RepositoryPackage(directory as Directory), excluded: plugin.excluded)); } } - /// Returns the files contained, recursively, within the plugins + /// Returns the files contained, recursively, within the packages /// involved in this command execution. Stream getFiles() { - return getTargetPackages() - .map((PackageEnumerationEntry entry) => entry.directory) - .asyncExpand((Directory folder) => getFilesForPackage(folder)); + return getTargetPackages().asyncExpand( + (PackageEnumerationEntry entry) => getFilesForPackage(entry.package)); } /// Returns the files contained, recursively, within [package]. - Stream getFilesForPackage(Directory package) { - return package + Stream getFilesForPackage(RepositoryPackage package) { + return package.directory .list(recursive: true, followLinks: false) .where((FileSystemEntity entity) => entity is File) .cast(); @@ -358,25 +363,6 @@ abstract class PluginCommand extends Command { return entity is Directory && entity.childFile('pubspec.yaml').existsSync(); } - /// Returns the example Dart packages contained in the specified plugin, or - /// an empty List, if the plugin has no examples. - Iterable getExamplesForPlugin(Directory plugin) { - final Directory exampleFolder = plugin.childDirectory('example'); - if (!exampleFolder.existsSync()) { - return []; - } - if (isFlutterPackage(exampleFolder)) { - return [exampleFolder]; - } - // Only look at the subdirectories of the example directory if the example - // directory itself is not a Dart package, and only look one level below the - // example directory for other dart packages. - return exampleFolder - .listSync() - .where((FileSystemEntity entity) => isFlutterPackage(entity)) - .cast(); - } - /// Retrieve an instance of [GitVersionFinder] based on `_kBaseSha` and [gitDir]. /// /// Throws tool exit if [gitDir] nor root directory is a git directory. diff --git a/script/tool/lib/src/common/plugin_utils.dart b/script/tool/lib/src/common/plugin_utils.dart index 0277b78d566a..d9c42e220c0b 100644 --- a/script/tool/lib/src/common/plugin_utils.dart +++ b/script/tool/lib/src/common/plugin_utils.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:file/file.dart'; +import 'package:flutter_plugin_tools/src/common/repository_package.dart'; import 'package:yaml/yaml.dart'; import 'core.dart'; @@ -16,7 +17,7 @@ enum PlatformSupport { federated, } -/// Returns whether the given directory contains a Flutter [platform] plugin. +/// Returns whether the given [package] is a Flutter [platform] plugin. /// /// It checks this by looking for the following pattern in the pubspec: /// @@ -27,7 +28,7 @@ enum PlatformSupport { /// /// If [requiredMode] is provided, the plugin must have the given type of /// implementation in order to return true. -bool pluginSupportsPlatform(String platform, FileSystemEntity entity, +bool pluginSupportsPlatform(String platform, RepositoryPackage package, {PlatformSupport? requiredMode}) { assert(platform == kPlatformIos || platform == kPlatformAndroid || @@ -35,14 +36,9 @@ bool pluginSupportsPlatform(String platform, FileSystemEntity entity, platform == kPlatformMacos || platform == kPlatformWindows || platform == kPlatformLinux); - if (entity is! Directory) { - return false; - } - try { - final File pubspecFile = entity.childFile('pubspec.yaml'); final YamlMap pubspecYaml = - loadYaml(pubspecFile.readAsStringSync()) as YamlMap; + loadYaml(package.pubspecFile.readAsStringSync()) as YamlMap; final YamlMap? flutterSection = pubspecYaml['flutter'] as YamlMap?; if (flutterSection == null) { return false; @@ -78,33 +74,3 @@ bool pluginSupportsPlatform(String platform, FileSystemEntity entity, return false; } } - -/// Returns whether the given directory contains a Flutter Android plugin. -bool isAndroidPlugin(FileSystemEntity entity) { - return pluginSupportsPlatform(kPlatformAndroid, entity); -} - -/// Returns whether the given directory contains a Flutter iOS plugin. -bool isIosPlugin(FileSystemEntity entity) { - return pluginSupportsPlatform(kPlatformIos, entity); -} - -/// Returns whether the given directory contains a Flutter web plugin. -bool isWebPlugin(FileSystemEntity entity) { - return pluginSupportsPlatform(kPlatformWeb, entity); -} - -/// Returns whether the given directory contains a Flutter Windows plugin. -bool isWindowsPlugin(FileSystemEntity entity) { - return pluginSupportsPlatform(kPlatformWindows, entity); -} - -/// Returns whether the given directory contains a Flutter macOS plugin. -bool isMacOsPlugin(FileSystemEntity entity) { - return pluginSupportsPlatform(kPlatformMacos, entity); -} - -/// Returns whether the given directory contains a Flutter linux plugin. -bool isLinuxPlugin(FileSystemEntity entity) { - return pluginSupportsPlatform(kPlatformLinux, entity); -} diff --git a/script/tool/lib/src/common/pub_version_finder.dart b/script/tool/lib/src/common/pub_version_finder.dart index ebac473de7ac..572cb913aa7d 100644 --- a/script/tool/lib/src/common/pub_version_finder.dart +++ b/script/tool/lib/src/common/pub_version_finder.dart @@ -27,10 +27,10 @@ class PubVersionFinder { /// Get the package version on pub. Future getPackageVersion( - {required String package}) async { - assert(package.isNotEmpty); + {required String packageName}) async { + assert(packageName.isNotEmpty); final Uri pubHostUri = Uri.parse(pubHost); - final Uri url = pubHostUri.replace(path: '/packages/$package.json'); + final Uri url = pubHostUri.replace(path: '/packages/$packageName.json'); final http.Response response = await httpClient.get(url); if (response.statusCode == 404) { diff --git a/script/tool/lib/src/common/repository_package.dart b/script/tool/lib/src/common/repository_package.dart new file mode 100644 index 000000000000..f6601d39b79e --- /dev/null +++ b/script/tool/lib/src/common/repository_package.dart @@ -0,0 +1,78 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:file/file.dart'; +import 'package:path/path.dart' as p; + +import 'core.dart'; + +/// A package in the repository. +// +// TODO(stuartmorgan): Add more package-related info here, such as an on-demand +// cache of the parsed pubspec. +class RepositoryPackage { + /// Creates a representation of the package at [directory]. + RepositoryPackage(this.directory); + + /// The location of the package. + final Directory directory; + + /// The path to the package. + String get path => directory.path; + + /// Returns the string to use when referring to the package in user-targeted + /// messages. + /// + /// Callers should not expect a specific format for this string, since + /// it uses heuristics to try to be precise without being overly verbose. If + /// an exact format (e.g., published name, or basename) is required, that + /// should be used instead. + String get displayName { + List components = directory.fileSystem.path.split(directory.path); + // Remove everything up to the packages directory. + final int packagesIndex = components.indexOf('packages'); + if (packagesIndex != -1) { + components = components.sublist(packagesIndex + 1); + } + // For the common federated plugin pattern of `foo/foo_subpackage`, drop + // the first part since it's not useful. + if (components.length >= 2 && + components[1].startsWith('${components[0]}_')) { + components = components.sublist(1); + } + return p.posix.joinAll(components); + } + + /// The package's top-level pubspec.yaml. + File get pubspecFile => directory.childFile('pubspec.yaml'); + + /// Returns the Flutter example packages contained in the package, if any. + Iterable getExamples() { + final Directory exampleDirectory = directory.childDirectory('example'); + if (!exampleDirectory.existsSync()) { + return []; + } + if (isFlutterPackage(exampleDirectory)) { + return [RepositoryPackage(exampleDirectory)]; + } + // Only look at the subdirectories of the example directory if the example + // directory itself is not a Dart package, and only look one level below the + // example directory for other Dart packages. + return exampleDirectory + .listSync() + .where((FileSystemEntity entity) => isFlutterPackage(entity)) + // isFlutterPackage guarantees that the cast to Directory is safe. + .map((FileSystemEntity entity) => + RepositoryPackage(entity as Directory)); + } + + /// Returns the example directory, assuming there is only one. + /// + /// DO NOT USE THIS METHOD. It exists only to easily find code that was + /// written to use a single example and needs to be restructured to handle + /// multiple examples. New code should always use [getExamples]. + // TODO(stuartmorgan): Eliminate all uses of this. + RepositoryPackage getSingleExampleDeprecated() => + RepositoryPackage(directory.childDirectory('example')); +} diff --git a/script/tool/lib/src/create_all_plugins_app_command.dart b/script/tool/lib/src/create_all_plugins_app_command.dart index e1cee6f3fe7d..6dbebf2f5c74 100644 --- a/script/tool/lib/src/create_all_plugins_app_command.dart +++ b/script/tool/lib/src/create_all_plugins_app_command.dart @@ -11,6 +11,7 @@ import 'package:pubspec_parse/pubspec_parse.dart'; import 'common/core.dart'; import 'common/plugin_command.dart'; +import 'common/repository_package.dart'; const String _outputDirectoryFlag = 'output-dir'; @@ -170,10 +171,11 @@ class CreateAllPluginsAppCommand extends PluginCommand { final Map pathDependencies = {}; - await for (final PackageEnumerationEntry package in getTargetPackages()) { + await for (final PackageEnumerationEntry entry in getTargetPackages()) { + final RepositoryPackage package = entry.package; final Directory pluginDirectory = package.directory; final String pluginName = pluginDirectory.basename; - final File pubspecFile = pluginDirectory.childFile('pubspec.yaml'); + final File pubspecFile = package.pubspecFile; final Pubspec pubspec = Pubspec.parse(pubspecFile.readAsStringSync()); if (pubspec.publishTo != 'none') { diff --git a/script/tool/lib/src/drive_examples_command.dart b/script/tool/lib/src/drive_examples_command.dart index 7e800ed54866..3605dcce1f22 100644 --- a/script/tool/lib/src/drive_examples_command.dart +++ b/script/tool/lib/src/drive_examples_command.dart @@ -12,6 +12,7 @@ import 'common/core.dart'; import 'common/package_looping_command.dart'; import 'common/plugin_utils.dart'; import 'common/process_runner.dart'; +import 'common/repository_package.dart'; const int _exitNoPlatformFlags = 2; const int _exitNoAvailableDevice = 3; @@ -119,9 +120,9 @@ class DriveExamplesCommand extends PackageLoopingCommand { } @override - Future runForPackage(Directory package) async { - if (package.basename.endsWith('_platform_interface') && - !package.childDirectory('example').existsSync()) { + Future runForPackage(RepositoryPackage package) async { + if (package.directory.basename.endsWith('_platform_interface') && + !package.getSingleExampleDeprecated().directory.existsSync()) { // Platform interface packages generally aren't intended to have // examples, and don't need integration tests, so skip rather than fail. return PackageResult.skip( @@ -140,16 +141,16 @@ class DriveExamplesCommand extends PackageLoopingCommand { // If there is no supported target platform, skip the plugin. if (deviceFlags.isEmpty) { return PackageResult.skip( - '${getPackageDescription(package)} does not support any requested platform.'); + '${package.displayName} does not support any requested platform.'); } int examplesFound = 0; bool testsRan = false; final List errors = []; - for (final Directory example in getExamplesForPlugin(package)) { + for (final RepositoryPackage example in package.getExamples()) { ++examplesFound; final String exampleName = - getRelativePosixPath(example, from: packagesDir); + getRelativePosixPath(example.directory, from: packagesDir); final List drivers = await _getDrivers(example); if (drivers.isEmpty) { @@ -173,7 +174,7 @@ class DriveExamplesCommand extends PackageLoopingCommand { if (testTargets.isEmpty) { final String driverRelativePath = - getRelativePosixPath(driver, from: package); + getRelativePosixPath(driver, from: package.directory); printError( 'Found $driverRelativePath, but no integration_test/*_test.dart files.'); errors.add('No test files for $driverRelativePath'); @@ -185,7 +186,8 @@ class DriveExamplesCommand extends PackageLoopingCommand { example, driver, testTargets, deviceFlags: deviceFlags); for (final File failingTarget in failingTargets) { - errors.add(getRelativePosixPath(failingTarget, from: package)); + errors.add( + getRelativePosixPath(failingTarget, from: package.directory)); } } } @@ -229,10 +231,10 @@ class DriveExamplesCommand extends PackageLoopingCommand { return deviceIds; } - Future> _getDrivers(Directory example) async { + Future> _getDrivers(RepositoryPackage example) async { final List drivers = []; - final Directory driverDir = example.childDirectory('test_driver'); + final Directory driverDir = example.directory.childDirectory('test_driver'); if (driverDir.existsSync()) { await for (final FileSystemEntity driver in driverDir.list()) { if (driver is File && driver.basename.endsWith('_test.dart')) { @@ -253,10 +255,10 @@ class DriveExamplesCommand extends PackageLoopingCommand { return testFile.existsSync() ? testFile : null; } - Future> _getIntegrationTests(Directory example) async { + Future> _getIntegrationTests(RepositoryPackage example) async { final List tests = []; final Directory integrationTestDir = - example.childDirectory('integration_test'); + example.directory.childDirectory('integration_test'); if (integrationTestDir.existsSync()) { await for (final FileSystemEntity file in integrationTestDir.list()) { @@ -278,7 +280,7 @@ class DriveExamplesCommand extends PackageLoopingCommand { /// - `['-d', 'web-server', '--web-port=', '--browser-name=]` /// for web Future> _driveTests( - Directory example, + RepositoryPackage example, File driver, List targets, { required List deviceFlags, @@ -296,11 +298,11 @@ class DriveExamplesCommand extends PackageLoopingCommand { if (enableExperiment.isNotEmpty) '--enable-experiment=$enableExperiment', '--driver', - getRelativePosixPath(driver, from: example), + getRelativePosixPath(driver, from: example.directory), '--target', - getRelativePosixPath(target, from: example), + getRelativePosixPath(target, from: example.directory), ], - workingDir: example); + workingDir: example.directory); if (exitCode != 0) { failures.add(target); } diff --git a/script/tool/lib/src/firebase_test_lab_command.dart b/script/tool/lib/src/firebase_test_lab_command.dart index fd2de97be4b3..4fc47c0da70c 100644 --- a/script/tool/lib/src/firebase_test_lab_command.dart +++ b/script/tool/lib/src/firebase_test_lab_command.dart @@ -13,6 +13,7 @@ import 'common/core.dart'; import 'common/gradle.dart'; import 'common/package_looping_command.dart'; import 'common/process_runner.dart'; +import 'common/repository_package.dart'; const int _exitGcloudAuthFailed = 2; @@ -117,13 +118,13 @@ class FirebaseTestLabCommand extends PackageLoopingCommand { } @override - Future runForPackage(Directory package) async { - final Directory exampleDirectory = package.childDirectory('example'); + Future runForPackage(RepositoryPackage package) async { + final RepositoryPackage example = package.getSingleExampleDeprecated(); final Directory androidDirectory = - exampleDirectory.childDirectory('android'); + example.directory.childDirectory('android'); if (!androidDirectory.existsSync()) { return PackageResult.skip( - '${getPackageDescription(exampleDirectory)} does not support Android.'); + '${example.displayName} does not support Android.'); } if (!androidDirectory @@ -137,7 +138,7 @@ class FirebaseTestLabCommand extends PackageLoopingCommand { } // Ensures that gradle wrapper exists - final GradleProject project = GradleProject(exampleDirectory, + final GradleProject project = GradleProject(example.directory, processRunner: processRunner, platform: platform); if (!await _ensureGradleWrapperExists(project)) { return PackageResult.fail(['Unable to build example apk']); @@ -155,7 +156,8 @@ class FirebaseTestLabCommand extends PackageLoopingCommand { // test file's run. int resultsCounter = 0; for (final File test in _findIntegrationTestFiles(package)) { - final String testName = getRelativePosixPath(test, from: package); + final String testName = + getRelativePosixPath(test, from: package.directory); print('Testing $testName...'); if (!await _runGradle(project, 'app:assembleDebug', testFile: test)) { printError('Could not build $testName'); @@ -165,7 +167,7 @@ class FirebaseTestLabCommand extends PackageLoopingCommand { final String buildId = getStringArg('build-id'); final String testRunId = getStringArg('test-run-id'); final String resultsDir = - 'plugins_android_test/${getPackageDescription(package)}/$buildId/$testRunId/${resultsCounter++}/'; + 'plugins_android_test/${package.displayName}/$buildId/$testRunId/${resultsCounter++}/'; final List args = [ 'firebase', 'test', @@ -186,7 +188,7 @@ class FirebaseTestLabCommand extends PackageLoopingCommand { args.addAll(['--device', device]); } final int exitCode = await processRunner.runAndStream('gcloud', args, - workingDir: exampleDirectory); + workingDir: example.directory); if (exitCode != 0) { printError('Test failure for $testName'); @@ -262,9 +264,11 @@ class FirebaseTestLabCommand extends PackageLoopingCommand { } /// Finds and returns all integration test files for [package]. - Iterable _findIntegrationTestFiles(Directory package) sync* { - final Directory integrationTestDir = - package.childDirectory('example').childDirectory('integration_test'); + Iterable _findIntegrationTestFiles(RepositoryPackage package) sync* { + final Directory integrationTestDir = package + .getSingleExampleDeprecated() + .directory + .childDirectory('integration_test'); if (!integrationTestDir.existsSync()) { return; diff --git a/script/tool/lib/src/lint_android_command.dart b/script/tool/lib/src/lint_android_command.dart index be6c6ed32415..a7b5c4f2e8bf 100644 --- a/script/tool/lib/src/lint_android_command.dart +++ b/script/tool/lib/src/lint_android_command.dart @@ -10,6 +10,7 @@ import 'common/core.dart'; import 'common/gradle.dart'; import 'common/package_looping_command.dart'; import 'common/process_runner.dart'; +import 'common/repository_package.dart'; /// Lint the CocoaPod podspecs and run unit tests. /// @@ -30,22 +31,22 @@ class LintAndroidCommand extends PackageLoopingCommand { 'Requires the example to have been build at least once before running.'; @override - Future runForPackage(Directory package) async { + Future runForPackage(RepositoryPackage package) async { if (!pluginSupportsPlatform(kPlatformAndroid, package, requiredMode: PlatformSupport.inline)) { return PackageResult.skip( 'Plugin does not have an Android implemenatation.'); } - final Directory exampleDirectory = package.childDirectory('example'); - final GradleProject project = GradleProject(exampleDirectory, + final RepositoryPackage example = package.getSingleExampleDeprecated(); + final GradleProject project = GradleProject(example.directory, processRunner: processRunner, platform: platform); if (!project.isConfigured()) { return PackageResult.fail(['Build example before linting']); } - final String packageName = package.basename; + final String packageName = package.directory.basename; // Only lint one build mode to avoid extra work. // Only lint the plugin project itself, to avoid failing due to errors in diff --git a/script/tool/lib/src/lint_podspecs_command.dart b/script/tool/lib/src/lint_podspecs_command.dart index d0d93fcb79b1..ee44a82da5b9 100644 --- a/script/tool/lib/src/lint_podspecs_command.dart +++ b/script/tool/lib/src/lint_podspecs_command.dart @@ -12,6 +12,7 @@ import 'package:platform/platform.dart'; import 'common/core.dart'; import 'common/package_looping_command.dart'; import 'common/process_runner.dart'; +import 'common/repository_package.dart'; const int _exitUnsupportedPlatform = 2; const int _exitPodNotInstalled = 3; @@ -64,7 +65,7 @@ class LintPodspecsCommand extends PackageLoopingCommand { } @override - Future runForPackage(Directory package) async { + Future runForPackage(RepositoryPackage package) async { final List errors = []; final List podspecs = await _podspecsToLint(package); @@ -82,7 +83,7 @@ class LintPodspecsCommand extends PackageLoopingCommand { : PackageResult.fail(errors); } - Future> _podspecsToLint(Directory package) async { + Future> _podspecsToLint(RepositoryPackage package) async { final List podspecs = await getFilesForPackage(package).where((File entity) { final String filePath = entity.path; diff --git a/script/tool/lib/src/list_command.dart b/script/tool/lib/src/list_command.dart index 29a8ceb12782..e45c09bfd2ef 100644 --- a/script/tool/lib/src/list_command.dart +++ b/script/tool/lib/src/list_command.dart @@ -6,6 +6,7 @@ import 'package:file/file.dart'; import 'package:platform/platform.dart'; import 'common/plugin_command.dart'; +import 'common/repository_package.dart'; /// A command to list different types of repository content. class ListCommand extends PluginCommand { @@ -39,23 +40,22 @@ class ListCommand extends PluginCommand { Future run() async { switch (getStringArg(_type)) { case _plugin: - await for (final PackageEnumerationEntry package - in getTargetPackages()) { - print(package.directory.path); + await for (final PackageEnumerationEntry entry in getTargetPackages()) { + print(entry.package.path); } break; case _example: - final Stream examples = getTargetPackages() - .map((PackageEnumerationEntry entry) => entry.directory) - .expand(getExamplesForPlugin); - await for (final Directory package in examples) { + final Stream examples = getTargetPackages() + .expand( + (PackageEnumerationEntry entry) => entry.package.getExamples()); + await for (final RepositoryPackage package in examples) { print(package.path); } break; case _package: - await for (final PackageEnumerationEntry package + await for (final PackageEnumerationEntry entry in getTargetPackagesAndSubpackages()) { - print(package.directory.path); + print(entry.package.path); } break; case _file: diff --git a/script/tool/lib/src/native_test_command.dart b/script/tool/lib/src/native_test_command.dart index 0bd2ab45f634..725cf23a2e9a 100644 --- a/script/tool/lib/src/native_test_command.dart +++ b/script/tool/lib/src/native_test_command.dart @@ -10,6 +10,7 @@ import 'common/gradle.dart'; import 'common/package_looping_command.dart'; import 'common/plugin_utils.dart'; import 'common/process_runner.dart'; +import 'common/repository_package.dart'; import 'common/xcode.dart'; const String _unitTestFlag = 'unit'; @@ -115,7 +116,7 @@ this command. } @override - Future runForPackage(Directory package) async { + Future runForPackage(RepositoryPackage package) async { final List testPlatforms = []; for (final String platform in _requestedPlatforms) { if (pluginSupportsPlatform(platform, package, @@ -171,23 +172,24 @@ this command. : PackageResult.success(); } - Future<_PlatformResult> _testAndroid(Directory plugin, _TestMode mode) async { - bool exampleHasUnitTests(Directory example) { - return example + Future<_PlatformResult> _testAndroid( + RepositoryPackage plugin, _TestMode mode) async { + bool exampleHasUnitTests(RepositoryPackage example) { + return example.directory .childDirectory('android') .childDirectory('app') .childDirectory('src') .childDirectory('test') .existsSync() || - example.parent + example.directory.parent .childDirectory('android') .childDirectory('src') .childDirectory('test') .existsSync(); } - bool exampleHasNativeIntegrationTests(Directory example) { - final Directory integrationTestDirectory = example + bool exampleHasNativeIntegrationTests(RepositoryPackage example) { + final Directory integrationTestDirectory = example.directory .childDirectory('android') .childDirectory('app') .childDirectory('src') @@ -216,12 +218,12 @@ this command. }); } - final Iterable examples = getExamplesForPlugin(plugin); + final Iterable examples = plugin.getExamples(); bool ranTests = false; bool failed = false; bool hasMissingBuild = false; - for (final Directory example in examples) { + for (final RepositoryPackage example in examples) { final bool hasUnitTests = exampleHasUnitTests(example); final bool hasIntegrationTests = exampleHasNativeIntegrationTests(example); @@ -239,11 +241,11 @@ this command. continue; } - final String exampleName = getPackageDescription(example); + final String exampleName = example.displayName; _printRunningExampleTestsMessage(example, 'Android'); final GradleProject project = GradleProject( - example, + example.directory, processRunner: processRunner, platform: platform, ); @@ -301,12 +303,12 @@ this command. return _PlatformResult(RunState.succeeded); } - Future<_PlatformResult> _testIos(Directory plugin, _TestMode mode) { + Future<_PlatformResult> _testIos(RepositoryPackage plugin, _TestMode mode) { return _runXcodeTests(plugin, 'iOS', mode, extraFlags: _iosDestinationFlags); } - Future<_PlatformResult> _testMacOS(Directory plugin, _TestMode mode) { + Future<_PlatformResult> _testMacOS(RepositoryPackage plugin, _TestMode mode) { return _runXcodeTests(plugin, 'macOS', mode); } @@ -316,7 +318,7 @@ this command. /// The tests targets must be added to the Xcode project of the example app, /// usually at "example/{ios,macos}/Runner.xcworkspace". Future<_PlatformResult> _runXcodeTests( - Directory plugin, + RepositoryPackage plugin, String platform, _TestMode mode, { List extraFlags = const [], @@ -330,11 +332,11 @@ this command. // Assume skipped until at least one test has run. RunState overallResult = RunState.skipped; - for (final Directory example in getExamplesForPlugin(plugin)) { - final String exampleName = getPackageDescription(example); + for (final RepositoryPackage example in plugin.getExamples()) { + final String exampleName = example.displayName; if (testTarget != null) { - final Directory project = example + final Directory project = example.directory .childDirectory(platform.toLowerCase()) .childDirectory('Runner.xcodeproj'); final bool? hasTarget = @@ -351,7 +353,7 @@ this command. _printRunningExampleTestsMessage(example, platform); final int exitCode = await _xcode.runXcodeBuild( - example, + example.directory, actions: ['test'], workspace: '${platform.toLowerCase()}/Runner.xcworkspace', scheme: 'Runner', @@ -387,20 +389,22 @@ this command. /// Prints a standard format message indicating that [platform] tests for /// [plugin]'s [example] are about to be run. - void _printRunningExampleTestsMessage(Directory example, String platform) { - print('Running $platform tests for ${getPackageDescription(example)}...'); + void _printRunningExampleTestsMessage( + RepositoryPackage example, String platform) { + print('Running $platform tests for ${example.displayName}...'); } /// Prints a standard format message indicating that no tests were found for /// [plugin]'s [example] for [platform]. - void _printNoExampleTestsMessage(Directory example, String platform) { - print('No $platform tests found for ${getPackageDescription(example)}'); + void _printNoExampleTestsMessage(RepositoryPackage example, String platform) { + print('No $platform tests found for ${example.displayName}'); } } // The type for a function that takes a plugin directory and runs its native // tests for a specific platform. -typedef _TestFunction = Future<_PlatformResult> Function(Directory, _TestMode); +typedef _TestFunction = Future<_PlatformResult> Function( + RepositoryPackage, _TestMode); /// A collection of information related to a specific platform. class _PlatformDetails { diff --git a/script/tool/lib/src/publish_check_command.dart b/script/tool/lib/src/publish_check_command.dart index fda68a6a74a4..ab9f5f147495 100644 --- a/script/tool/lib/src/publish_check_command.dart +++ b/script/tool/lib/src/publish_check_command.dart @@ -16,6 +16,7 @@ import 'common/core.dart'; import 'common/package_looping_command.dart'; import 'common/process_runner.dart'; import 'common/pub_version_finder.dart'; +import 'common/repository_package.dart'; /// A command to check that packages are publishable via 'dart publish'. class PublishCheckCommand extends PackageLoopingCommand { @@ -75,7 +76,7 @@ class PublishCheckCommand extends PackageLoopingCommand { } @override - Future runForPackage(Directory package) async { + Future runForPackage(RepositoryPackage package) async { final _PublishCheckResult? result = await _passesPublishCheck(package); if (result == null) { return PackageResult.skip('Package is marked as unpublishable.'); @@ -114,8 +115,8 @@ class PublishCheckCommand extends PackageLoopingCommand { } } - Pubspec? _tryParsePubspec(Directory package) { - final File pubspecFile = package.childFile('pubspec.yaml'); + Pubspec? _tryParsePubspec(RepositoryPackage package) { + final File pubspecFile = package.pubspecFile; try { return Pubspec.parse(pubspecFile.readAsStringSync()); @@ -127,12 +128,12 @@ class PublishCheckCommand extends PackageLoopingCommand { } } - Future _hasValidPublishCheckRun(Directory package) async { + Future _hasValidPublishCheckRun(RepositoryPackage package) async { print('Running pub publish --dry-run:'); final io.Process process = await processRunner.start( flutterCommand, ['pub', 'publish', '--', '--dry-run'], - workingDirectory: package, + workingDirectory: package.directory, ); final StringBuffer outputBuffer = StringBuffer(); @@ -183,8 +184,9 @@ class PublishCheckCommand extends PackageLoopingCommand { /// Returns the result of the publish check, or null if the package is marked /// as unpublishable. - Future<_PublishCheckResult?> _passesPublishCheck(Directory package) async { - final String packageName = package.basename; + Future<_PublishCheckResult?> _passesPublishCheck( + RepositoryPackage package) async { + final String packageName = package.directory.basename; final Pubspec? pubspec = _tryParsePubspec(package); if (pubspec == null) { print('no pubspec'); @@ -219,7 +221,7 @@ class PublishCheckCommand extends PackageLoopingCommand { Future<_PublishCheckResult> _checkPublishingStatus( {required String packageName, required Version? version}) async { final PubVersionFinderResponse pubVersionFinderResponse = - await _pubVersionFinder.getPackageVersion(package: packageName); + await _pubVersionFinder.getPackageVersion(packageName: packageName); switch (pubVersionFinderResponse.result) { case PubVersionFinderResult.success: return pubVersionFinderResponse.versions.contains(version) diff --git a/script/tool/lib/src/publish_plugin_command.dart b/script/tool/lib/src/publish_plugin_command.dart index 8bcb9e37e8ef..6e1658f6f6e2 100644 --- a/script/tool/lib/src/publish_plugin_command.dart +++ b/script/tool/lib/src/publish_plugin_command.dart @@ -140,9 +140,9 @@ class PublishPluginCommand extends PluginCommand { @override Future run() async { - final String package = getStringArg(_packageOption); + final String packageName = getStringArg(_packageOption); final bool publishAllChanged = getBoolArg(_allChangedFlag); - if (package.isEmpty && !publishAllChanged) { + if (packageName.isEmpty && !publishAllChanged) { _print( 'Must specify a package to publish. See `plugin_tools help publish-plugin`.'); throw ToolExit(1); @@ -176,7 +176,7 @@ class PublishPluginCommand extends PluginCommand { ); } else { successful = await _publishAndTagPackage( - packageDir: _getPackageDir(package), + packageDir: _getPackageDir(packageName), remoteForTagPush: remote, ); } @@ -202,7 +202,7 @@ class PublishPluginCommand extends PluginCommand { await baseGitDir.runCommand(['tag', '--sort=-committerdate']); final List existingTags = (existingTagsResult.stdout as String) .split('\n') - ..removeWhere((String element) => element.isEmpty); + ..removeWhere((String element) => element.isEmpty); final List packagesReleased = []; final List packagesFailed = []; @@ -307,7 +307,7 @@ Safe to ignore if the package is deleted in this commit. // Check if the package named `packageName` with `version` has already published. final Version version = pubspec.version!; final PubVersionFinderResponse pubVersionFinderResponse = - await _pubVersionFinder.getPackageVersion(package: pubspec.name); + await _pubVersionFinder.getPackageVersion(packageName: pubspec.name); if (pubVersionFinderResponse.versions.contains(version)) { final String tagsForPackageWithSameVersion = existingTags.firstWhere( (String tag) => @@ -390,8 +390,8 @@ Safe to ignore if the package is deleted in this commit. // Returns the packageDirectory based on the package name. // Throws ToolExit if the `package` doesn't exist. - Directory _getPackageDir(String package) { - final Directory packageDir = packagesDir.childDirectory(package); + Directory _getPackageDir(String packageName) { + final Directory packageDir = packagesDir.childDirectory(packageName); if (!packageDir.existsSync()) { _print('${packageDir.absolute.path} does not exist.'); throw ToolExit(1); diff --git a/script/tool/lib/src/pubspec_check_command.dart b/script/tool/lib/src/pubspec_check_command.dart index 0a066ab72baf..def2adaf2788 100644 --- a/script/tool/lib/src/pubspec_check_command.dart +++ b/script/tool/lib/src/pubspec_check_command.dart @@ -10,6 +10,7 @@ import 'package:pubspec_parse/pubspec_parse.dart'; import 'common/core.dart'; import 'common/package_looping_command.dart'; import 'common/process_runner.dart'; +import 'common/repository_package.dart'; /// A command to enforce pubspec conventions across the repository. /// @@ -64,8 +65,8 @@ class PubspecCheckCommand extends PackageLoopingCommand { bool get includeSubpackages => true; @override - Future runForPackage(Directory package) async { - final File pubspec = package.childFile('pubspec.yaml'); + Future runForPackage(RepositoryPackage package) async { + final File pubspec = package.pubspecFile; final bool passesCheck = !pubspec.existsSync() || await _checkPubspec(pubspec, package: package); if (!passesCheck) { @@ -76,7 +77,7 @@ class PubspecCheckCommand extends PackageLoopingCommand { Future _checkPubspec( File pubspecFile, { - required Directory package, + required RepositoryPackage package, }) async { final String contents = pubspecFile.readAsStringSync(); final Pubspec? pubspec = _tryParsePubspec(contents); @@ -154,7 +155,7 @@ class PubspecCheckCommand extends PackageLoopingCommand { List _checkForRepositoryLinkErrors( Pubspec pubspec, { - required Directory package, + required RepositoryPackage package, }) { final List errorMessages = []; if (pubspec.repository == null) { @@ -189,12 +190,12 @@ class PubspecCheckCommand extends PackageLoopingCommand { // Should only be called on plugin packages. String? _checkForImplementsError( Pubspec pubspec, { - required Directory package, + required RepositoryPackage package, }) { if (_isImplementationPackage(package)) { final String? implements = pubspec.flutter!['plugin']!['implements'] as String?; - final String expectedImplements = package.parent.basename; + final String expectedImplements = package.directory.parent.basename; if (implements == null) { return 'Missing "implements: $expectedImplements" in "plugin" section.'; } else if (implements != expectedImplements) { @@ -207,13 +208,13 @@ class PubspecCheckCommand extends PackageLoopingCommand { // Returns true if [packageName] appears to be an implementation package // according to repository conventions. - bool _isImplementationPackage(Directory package) { + bool _isImplementationPackage(RepositoryPackage package) { // An implementation package should be in a group folder... - final Directory parentDir = package.parent; + final Directory parentDir = package.directory.parent; if (parentDir.path == packagesDir.path) { return false; } - final String packageName = package.basename; + final String packageName = package.directory.basename; final String parentName = parentDir.basename; // ... whose name is a prefix of the package name. if (!packageName.startsWith(parentName)) { diff --git a/script/tool/lib/src/test_command.dart b/script/tool/lib/src/test_command.dart index 9dfe66b7926a..5a0b43d3b223 100644 --- a/script/tool/lib/src/test_command.dart +++ b/script/tool/lib/src/test_command.dart @@ -9,6 +9,7 @@ import 'common/core.dart'; import 'common/package_looping_command.dart'; import 'common/plugin_utils.dart'; import 'common/process_runner.dart'; +import 'common/repository_package.dart'; /// A command to run Dart unit tests for packages. class TestCommand extends PackageLoopingCommand { @@ -36,13 +37,13 @@ class TestCommand extends PackageLoopingCommand { 'This command requires "flutter" to be in your path.'; @override - Future runForPackage(Directory package) async { - if (!package.childDirectory('test').existsSync()) { + Future runForPackage(RepositoryPackage package) async { + if (!package.directory.childDirectory('test').existsSync()) { return PackageResult.skip('No test/ directory.'); } bool passed; - if (isFlutterPackage(package)) { + if (isFlutterPackage(package.directory)) { passed = await _runFlutterTests(package); } else { passed = await _runDartTests(package); @@ -51,7 +52,7 @@ class TestCommand extends PackageLoopingCommand { } /// Runs the Dart tests for a Flutter package, returning true on success. - Future _runFlutterTests(Directory package) async { + Future _runFlutterTests(RepositoryPackage package) async { final String experiment = getStringArg(kEnableExperiment); final int exitCode = await processRunner.runAndStream( @@ -61,21 +62,21 @@ class TestCommand extends PackageLoopingCommand { '--color', if (experiment.isNotEmpty) '--enable-experiment=$experiment', // TODO(ditman): Remove this once all plugins are migrated to 'drive'. - if (isWebPlugin(package)) '--platform=chrome', + if (pluginSupportsPlatform(kPlatformWeb, package)) '--platform=chrome', ], - workingDir: package, + workingDir: package.directory, ); return exitCode == 0; } /// Runs the Dart tests for a non-Flutter package, returning true on success. - Future _runDartTests(Directory package) async { + Future _runDartTests(RepositoryPackage package) async { // Unlike `flutter test`, `pub run test` does not automatically get // packages int exitCode = await processRunner.runAndStream( 'dart', ['pub', 'get'], - workingDir: package, + workingDir: package.directory, ); if (exitCode != 0) { printError('Unable to fetch dependencies.'); @@ -92,7 +93,7 @@ class TestCommand extends PackageLoopingCommand { if (experiment.isNotEmpty) '--enable-experiment=$experiment', 'test', ], - workingDir: package, + workingDir: package.directory, ); return exitCode == 0; diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart index 67c563782888..67a81b967a8e 100644 --- a/script/tool/lib/src/version_check_command.dart +++ b/script/tool/lib/src/version_check_command.dart @@ -16,6 +16,7 @@ import 'common/git_version_finder.dart'; import 'common/package_looping_command.dart'; import 'common/process_runner.dart'; import 'common/pub_version_finder.dart'; +import 'common/repository_package.dart'; /// Categories of version change types. enum NextVersionType { @@ -133,7 +134,7 @@ class VersionCheckCommand extends PackageLoopingCommand { Future initializeRun() async {} @override - Future runForPackage(Directory package) async { + Future runForPackage(RepositoryPackage package) async { final Pubspec? pubspec = _tryParsePubspec(package); if (pubspec == null) { // No remaining checks make sense, so fail immediately. @@ -196,7 +197,7 @@ class VersionCheckCommand extends PackageLoopingCommand { /// the name from pubspec.yaml, not the on disk name if different.) Future _fetchPreviousVersionFromPub(String packageName) async { final PubVersionFinderResponse pubVersionFinderResponse = - await _pubVersionFinder.getPackageVersion(package: packageName); + await _pubVersionFinder.getPackageVersion(packageName: packageName); switch (pubVersionFinderResponse.result) { case PubVersionFinderResult.success: return pubVersionFinderResponse.versions.first; @@ -214,10 +215,10 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body} /// Returns the version of [package] from git at the base comparison hash. Future _getPreviousVersionFromGit( - Directory package, { + RepositoryPackage package, { required GitVersionFinder gitVersionFinder, }) async { - final File pubspecFile = package.childFile('pubspec.yaml'); + final File pubspecFile = package.pubspecFile; final String relativePath = path.relative(pubspecFile.absolute.path, from: (await gitDir).path); // Use Posix-style paths for git. @@ -230,7 +231,7 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body} /// Returns the state of the verison of [package] relative to the comparison /// base (git or pub, depending on flags). Future<_CurrentVersionState> _getVersionState( - Directory package, { + RepositoryPackage package, { required Pubspec pubspec, }) async { // This method isn't called unless `version` is non-null. @@ -310,7 +311,7 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body} /// /// Returns false if the CHANGELOG fails validation. Future _validateChangelogVersion( - Directory package, { + RepositoryPackage package, { required Pubspec pubspec, required bool pubspecVersionChanged, }) async { @@ -318,7 +319,7 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body} final Version fromPubspec = pubspec.version!; // get first version from CHANGELOG - final File changelog = package.childFile('CHANGELOG.md'); + final File changelog = package.directory.childFile('CHANGELOG.md'); final List lines = changelog.readAsLinesSync(); String? firstLineWithText; final Iterator iterator = lines.iterator; @@ -386,8 +387,8 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog. return true; } - Pubspec? _tryParsePubspec(Directory package) { - final File pubspecFile = package.childFile('pubspec.yaml'); + Pubspec? _tryParsePubspec(RepositoryPackage package) { + final File pubspecFile = package.pubspecFile; try { final Pubspec pubspec = Pubspec.parse(pubspecFile.readAsStringSync()); diff --git a/script/tool/lib/src/xcode_analyze_command.dart b/script/tool/lib/src/xcode_analyze_command.dart index 27cd8c435142..3d34dab9f087 100644 --- a/script/tool/lib/src/xcode_analyze_command.dart +++ b/script/tool/lib/src/xcode_analyze_command.dart @@ -9,6 +9,7 @@ import 'common/core.dart'; import 'common/package_looping_command.dart'; import 'common/plugin_utils.dart'; import 'common/process_runner.dart'; +import 'common/repository_package.dart'; import 'common/xcode.dart'; /// The command to run Xcode's static analyzer on plugins. @@ -42,7 +43,7 @@ class XcodeAnalyzeCommand extends PackageLoopingCommand { } @override - Future runForPackage(Directory package) async { + Future runForPackage(RepositoryPackage package) async { final bool testIos = getBoolArg(kPlatformIos) && pluginSupportsPlatform(kPlatformIos, package, requiredMode: PlatformSupport.inline); @@ -78,18 +79,18 @@ class XcodeAnalyzeCommand extends PackageLoopingCommand { /// Analyzes [plugin] for [platform], returning true if it passed analysis. Future _analyzePlugin( - Directory plugin, + RepositoryPackage plugin, String platform, { List extraFlags = const [], }) async { bool passing = true; - for (final Directory example in getExamplesForPlugin(plugin)) { + for (final RepositoryPackage example in plugin.getExamples()) { // Running tests and static analyzer. - final String examplePath = - getRelativePosixPath(example, from: plugin.parent); + final String examplePath = getRelativePosixPath(example.directory, + from: plugin.directory.parent); print('Running $platform tests and analyzer for $examplePath...'); final int exitCode = await _xcode.runXcodeBuild( - example, + example.directory, actions: ['analyze'], workspace: '${platform.toLowerCase()}/Runner.xcworkspace', scheme: 'Runner', diff --git a/script/tool/test/common/package_looping_command_test.dart b/script/tool/test/common/package_looping_command_test.dart index 00e64ddc21fe..721923ae9c6e 100644 --- a/script/tool/test/common/package_looping_command_test.dart +++ b/script/tool/test/common/package_looping_command_test.dart @@ -11,6 +11,7 @@ import 'package:file/memory.dart'; import 'package:flutter_plugin_tools/src/common/core.dart'; import 'package:flutter_plugin_tools/src/common/package_looping_command.dart'; import 'package:flutter_plugin_tools/src/common/process_runner.dart'; +import 'package:flutter_plugin_tools/src/common/repository_package.dart'; import 'package:git/git.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; @@ -578,64 +579,6 @@ void main() { ])); }); }); - - group('utility', () { - test('getPackageDescription prints packageDir-relative paths by default', - () async { - final TestPackageLoopingCommand command = - TestPackageLoopingCommand(packagesDir, platform: mockPlatform); - - expect( - command.getPackageDescription(packagesDir.childDirectory('foo')), - 'foo', - ); - expect( - command.getPackageDescription(packagesDir - .childDirectory('foo') - .childDirectory('bar') - .childDirectory('baz')), - 'foo/bar/baz', - ); - }); - - test('getPackageDescription always uses Posix-style paths', () async { - mockPlatform.isWindows = true; - final TestPackageLoopingCommand command = - TestPackageLoopingCommand(packagesDir, platform: mockPlatform); - - expect( - command.getPackageDescription(packagesDir.childDirectory('foo')), - 'foo', - ); - expect( - command.getPackageDescription(packagesDir - .childDirectory('foo') - .childDirectory('bar') - .childDirectory('baz')), - 'foo/bar/baz', - ); - }); - - test( - 'getPackageDescription elides group name in grouped federated plugin structure', - () async { - final TestPackageLoopingCommand command = - TestPackageLoopingCommand(packagesDir, platform: mockPlatform); - - expect( - command.getPackageDescription(packagesDir - .childDirectory('a_plugin') - .childDirectory('a_plugin_platform_interface')), - 'a_plugin_platform_interface', - ); - expect( - command.getPackageDescription(packagesDir - .childDirectory('a_plugin') - .childDirectory('a_plugin_web')), - 'a_plugin_web', - ); - }); - }); } class TestPackageLoopingCommand extends PackageLoopingCommand { @@ -699,18 +642,18 @@ class TestPackageLoopingCommand extends PackageLoopingCommand { } @override - Future runForPackage(Directory package) async { + Future runForPackage(RepositoryPackage package) async { checkedPackages.add(package.path); - final File warningFile = package.childFile(_warningFile); + final File warningFile = package.directory.childFile(_warningFile); if (warningFile.existsSync()) { final List warnings = warningFile.readAsLinesSync(); warnings.forEach(logWarning); } - final File skipFile = package.childFile(_skipFile); + final File skipFile = package.directory.childFile(_skipFile); if (skipFile.existsSync()) { return PackageResult.skip(skipFile.readAsStringSync()); } - final File errorFile = package.childFile(_errorFile); + final File errorFile = package.directory.childFile(_errorFile); if (errorFile.existsSync()) { return PackageResult.fail(errorFile.readAsLinesSync()); } diff --git a/script/tool/test/common/plugin_command_test.dart b/script/tool/test/common/plugin_command_test.dart index 2f332aa8eb55..10bdff4e9c56 100644 --- a/script/tool/test/common/plugin_command_test.dart +++ b/script/tool/test/common/plugin_command_test.dart @@ -498,7 +498,7 @@ packages/plugin3/plugin3.dart expect( localCommand.plugins, unorderedEquals(expectedShards[i] - .map((Directory package) => package.path) + .map((Directory packageDir) => packageDir.path) .toList())); } }); @@ -541,7 +541,7 @@ packages/plugin3/plugin3.dart expect( localCommand.plugins, unorderedEquals(expectedShards[i] - .map((Directory package) => package.path) + .map((Directory packageDir) => packageDir.path) .toList())); } }); @@ -594,7 +594,7 @@ packages/plugin3/plugin3.dart expect( localCommand.plugins, unorderedEquals(expectedShards[i] - .map((Directory package) => package.path) + .map((Directory packageDir) => packageDir.path) .toList())); } }); @@ -620,8 +620,8 @@ class SamplePluginCommand extends PluginCommand { @override Future run() async { - await for (final PackageEnumerationEntry package in getTargetPackages()) { - plugins.add(package.directory.path); + await for (final PackageEnumerationEntry entry in getTargetPackages()) { + plugins.add(entry.package.path); } } } diff --git a/script/tool/test/common/plugin_utils_test.dart b/script/tool/test/common/plugin_utils_test.dart index c32c3f8e02bf..7f1ba2add00a 100644 --- a/script/tool/test/common/plugin_utils_test.dart +++ b/script/tool/test/common/plugin_utils_test.dart @@ -6,6 +6,7 @@ import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_plugin_tools/src/common/core.dart'; import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; +import 'package:flutter_plugin_tools/src/common/repository_package.dart'; import 'package:test/test.dart'; import '../util.dart'; @@ -21,7 +22,8 @@ void main() { group('pluginSupportsPlatform', () { test('no platforms', () async { - final Directory plugin = createFakePlugin('plugin', packagesDir); + final RepositoryPackage plugin = + RepositoryPackage(createFakePlugin('plugin', packagesDir)); expect(pluginSupportsPlatform(kPlatformAndroid, plugin), isFalse); expect(pluginSupportsPlatform(kPlatformIos, plugin), isFalse); @@ -32,7 +34,8 @@ void main() { }); test('all platforms', () async { - final Directory plugin = createFakePlugin('plugin', packagesDir, + final RepositoryPackage plugin = RepositoryPackage(createFakePlugin( + 'plugin', packagesDir, platformSupport: { kPlatformAndroid: PlatformSupport.inline, kPlatformIos: PlatformSupport.inline, @@ -40,7 +43,7 @@ void main() { kPlatformMacos: PlatformSupport.inline, kPlatformWeb: PlatformSupport.inline, kPlatformWindows: PlatformSupport.inline, - }); + })); expect(pluginSupportsPlatform(kPlatformAndroid, plugin), isTrue); expect(pluginSupportsPlatform(kPlatformIos, plugin), isTrue); @@ -51,7 +54,7 @@ void main() { }); test('some platforms', () async { - final Directory plugin = createFakePlugin( + final RepositoryPackage plugin = RepositoryPackage(createFakePlugin( 'plugin', packagesDir, platformSupport: { @@ -59,7 +62,7 @@ void main() { kPlatformLinux: PlatformSupport.inline, kPlatformWeb: PlatformSupport.inline, }, - ); + )); expect(pluginSupportsPlatform(kPlatformAndroid, plugin), isTrue); expect(pluginSupportsPlatform(kPlatformIos, plugin), isFalse); @@ -70,7 +73,7 @@ void main() { }); test('inline plugins are only detected as inline', () async { - final Directory plugin = createFakePlugin( + final RepositoryPackage plugin = RepositoryPackage(createFakePlugin( 'plugin', packagesDir, platformSupport: { @@ -81,7 +84,7 @@ void main() { kPlatformWeb: PlatformSupport.inline, kPlatformWindows: PlatformSupport.inline, }, - ); + )); expect( pluginSupportsPlatform(kPlatformAndroid, plugin, @@ -135,7 +138,7 @@ void main() { test('federated plugins are only detected as federated', () async { const String pluginName = 'plugin'; - final Directory plugin = createFakePlugin( + final RepositoryPackage plugin = RepositoryPackage(createFakePlugin( pluginName, packagesDir, platformSupport: { @@ -146,7 +149,7 @@ void main() { kPlatformWeb: PlatformSupport.federated, kPlatformWindows: PlatformSupport.federated, }, - ); + )); expect( pluginSupportsPlatform(kPlatformAndroid, plugin, diff --git a/script/tool/test/common/pub_version_finder_test.dart b/script/tool/test/common/pub_version_finder_test.dart index 7d8658a907ee..1692cf214abe 100644 --- a/script/tool/test/common/pub_version_finder_test.dart +++ b/script/tool/test/common/pub_version_finder_test.dart @@ -19,7 +19,7 @@ void main() { }); final PubVersionFinder finder = PubVersionFinder(httpClient: mockClient); final PubVersionFinderResponse response = - await finder.getPackageVersion(package: 'some_package'); + await finder.getPackageVersion(packageName: 'some_package'); expect(response.versions, isEmpty); expect(response.result, PubVersionFinderResult.noPackageFound); @@ -33,7 +33,7 @@ void main() { }); final PubVersionFinder finder = PubVersionFinder(httpClient: mockClient); final PubVersionFinderResponse response = - await finder.getPackageVersion(package: 'some_package'); + await finder.getPackageVersion(packageName: 'some_package'); expect(response.versions, isEmpty); expect(response.result, PubVersionFinderResult.fail); @@ -64,7 +64,7 @@ void main() { }); final PubVersionFinder finder = PubVersionFinder(httpClient: mockClient); final PubVersionFinderResponse response = - await finder.getPackageVersion(package: 'some_package'); + await finder.getPackageVersion(packageName: 'some_package'); expect(response.versions, [ Version.parse('2.0.0'), diff --git a/script/tool/test/common/repository_package_test.dart b/script/tool/test/common/repository_package_test.dart new file mode 100644 index 000000000000..5c5624312f51 --- /dev/null +++ b/script/tool/test/common/repository_package_test.dart @@ -0,0 +1,123 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_plugin_tools/src/common/repository_package.dart'; +import 'package:test/test.dart'; + +import '../util.dart'; + +void main() { + late FileSystem fileSystem; + late Directory packagesDir; + + setUp(() { + fileSystem = MemoryFileSystem(); + packagesDir = createPackagesDirectory(fileSystem: fileSystem); + }); + + group('displayName', () { + test('prints packageDir-relative paths by default', () async { + expect( + RepositoryPackage(packagesDir.childDirectory('foo')).displayName, + 'foo', + ); + expect( + RepositoryPackage(packagesDir + .childDirectory('foo') + .childDirectory('bar') + .childDirectory('baz')) + .displayName, + 'foo/bar/baz', + ); + }); + + test('handles third_party/packages/', () async { + expect( + RepositoryPackage(packagesDir.parent + .childDirectory('third_party') + .childDirectory('packages') + .childDirectory('foo') + .childDirectory('bar') + .childDirectory('baz')) + .displayName, + 'foo/bar/baz', + ); + }); + + test('always uses Posix-style paths', () async { + final Directory windowsPackagesDir = createPackagesDirectory( + fileSystem: MemoryFileSystem(style: FileSystemStyle.windows)); + + expect( + RepositoryPackage(windowsPackagesDir.childDirectory('foo')).displayName, + 'foo', + ); + expect( + RepositoryPackage(windowsPackagesDir + .childDirectory('foo') + .childDirectory('bar') + .childDirectory('baz')) + .displayName, + 'foo/bar/baz', + ); + }); + + test('elides group name in grouped federated plugin structure', () async { + expect( + RepositoryPackage(packagesDir + .childDirectory('a_plugin') + .childDirectory('a_plugin_platform_interface')) + .displayName, + 'a_plugin_platform_interface', + ); + expect( + RepositoryPackage(packagesDir + .childDirectory('a_plugin') + .childDirectory('a_plugin_platform_web')) + .displayName, + 'a_plugin_platform_web', + ); + }); + + // The app-facing package doesn't get elided to avoid potential confusion + // with the group folder itself. + test('does not elide group name for app-facing packages', () async { + expect( + RepositoryPackage(packagesDir + .childDirectory('a_plugin') + .childDirectory('a_plugin')) + .displayName, + 'a_plugin/a_plugin', + ); + }); + }); + + group('getExamples', () { + test('handles a single example', () async { + final Directory plugin = createFakePlugin('a_plugin', packagesDir); + + final List examples = + RepositoryPackage(plugin).getExamples().toList(); + + expect(examples.length, 1); + expect(examples[0].path, plugin.childDirectory('example').path); + }); + + test('handles multiple examples', () async { + final Directory plugin = createFakePlugin('a_plugin', packagesDir, + examples: ['example1', 'example2']); + + final List examples = + RepositoryPackage(plugin).getExamples().toList(); + + expect(examples.length, 2); + expect(examples[0].path, + plugin.childDirectory('example').childDirectory('example1').path); + expect(examples[1].path, + plugin.childDirectory('example').childDirectory('example2').path); + }); + }); +} diff --git a/script/tool/test/format_command_test.dart b/script/tool/test/format_command_test.dart index b072e5d30aaf..b1c7eb14aed9 100644 --- a/script/tool/test/format_command_test.dart +++ b/script/tool/test/format_command_test.dart @@ -49,10 +49,10 @@ void main() { /// Returns a modified version of a list of [relativePaths] that are relative /// to [package] to instead be relative to [packagesDir]. List _getPackagesDirRelativePaths( - Directory package, List relativePaths) { + Directory packageDir, List relativePaths) { final p.Context path = analyzeCommand.path; final String relativeBase = - path.relative(package.path, from: packagesDir.path); + path.relative(packageDir.path, from: packagesDir.path); return relativePaths .map((String relativePath) => path.join(relativeBase, relativePath)) .toList(); diff --git a/script/tool/test/util.dart b/script/tool/test/util.dart index 1984a25cc430..7cc07939a57b 100644 --- a/script/tool/test/util.dart +++ b/script/tool/test/util.dart @@ -46,6 +46,7 @@ Directory createPackagesDirectory( /// /// [extraFiles] is an optional list of plugin-relative paths, using Posix /// separators, of extra files to create in the plugin. +// TODO(stuartmorgan): Convert the return to a RepositoryPackage. Directory createFakePlugin( String name, Directory parentDirectory, { @@ -77,6 +78,7 @@ Directory createFakePlugin( /// /// [extraFiles] is an optional list of package-relative paths, using unix-style /// separators, of extra files to create in the package. +// TODO(stuartmorgan): Convert the return to a RepositoryPackage. Directory createFakePackage( String name, Directory parentDirectory, {