diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md index 4bc8c56d7809..55aeacaf445b 100644 --- a/script/tool/CHANGELOG.md +++ b/script/tool/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.14.2 + +* Ensures that pub commands use `flutter` or `dart` depending on whether the + package requires Flutter for analysis and publishing, to support + non-Flutter-based repositories. +* Adds `--skip-if-not-supporting-dart-version` to support package constraints + via Dart versions rather than Flutter versions. + ## 0.14.1 * Adds `min_dart` as an alternative to `min_flutter` in tool configuration. diff --git a/script/tool/lib/src/analyze_command.dart b/script/tool/lib/src/analyze_command.dart index 91f247eed2c2..9f32195e9bdc 100644 --- a/script/tool/lib/src/analyze_command.dart +++ b/script/tool/lib/src/analyze_command.dart @@ -13,6 +13,7 @@ import 'common/gradle.dart'; import 'common/output_utils.dart'; import 'common/package_looping_command.dart'; import 'common/plugin_utils.dart'; +import 'common/pub_utils.dart'; import 'common/repository_package.dart'; import 'common/xcode.dart'; @@ -357,12 +358,13 @@ class AnalyzeCommand extends PackageLoopingCommand { } Future _runPubCommand(RepositoryPackage package, String command) async { - final int exitCode = await processRunner.runAndStream( - flutterCommand, - ['pub', command], - workingDir: package.directory, + return runPubCommand( + [command], + package, + processRunner, + platform, + dartSdkPathOverride: _dartBinaryPath, ); - return exitCode == 0; } /// Runs Gradle lint analysis for the given package, and returns the result diff --git a/script/tool/lib/src/common/package_looping_command.dart b/script/tool/lib/src/common/package_looping_command.dart index 53c885bcf6e7..04a284f7b720 100644 --- a/script/tool/lib/src/common/package_looping_command.dart +++ b/script/tool/lib/src/common/package_looping_command.dart @@ -95,11 +95,20 @@ abstract class PackageLoopingCommand extends PackageCommand { 'the provided version, or a Dart version newer than the ' 'corresponding Dart version.', ); + argParser.addOption( + _skipByDartVersionArg, + help: + 'Skip any packages that require a Dart version newer than the ' + 'provided version.', + ); } static const String _skipByFlutterVersionArg = 'skip-if-not-supporting-flutter-version'; + static const String _skipByDartVersionArg = + 'skip-if-not-supporting-dart-version'; + /// Packages that had at least one [logWarning] call. final Set _packagesWithWarnings = {}; @@ -289,9 +298,15 @@ abstract class PackageLoopingCommand extends PackageCommand { final Version? minFlutterVersion = minFlutterVersionArg.isEmpty ? null : Version.parse(minFlutterVersionArg); - final Version? minDartVersion = minFlutterVersion == null - ? null - : getDartSdkForFlutterSdk(minFlutterVersion); + + // Use the explicit Dart min if provided, otherwise fall back to the + // version corresponding to the Flutter min if that was provided. + final String minDartVersionArg = getStringArg(_skipByDartVersionArg); + final Version? minDartVersion = minDartVersionArg.isNotEmpty + ? Version.parse(minDartVersionArg) + : (minFlutterVersion == null + ? null + : getDartSdkForFlutterSdk(minFlutterVersion)); final runStart = DateTime.now(); diff --git a/script/tool/lib/src/common/pub_utils.dart b/script/tool/lib/src/common/pub_utils.dart index 963a548aef68..7bed2da631b6 100644 --- a/script/tool/lib/src/common/pub_utils.dart +++ b/script/tool/lib/src/common/pub_utils.dart @@ -12,27 +12,42 @@ import 'repository_package.dart'; /// Runs either `dart pub get` or `flutter pub get` in [package], depending on /// the package type. /// -/// If [alwaysUseFlutter] is true, it will use `flutter pub get` regardless. -/// This can be useful, for instance, to get the `flutter`-default behavior -/// of fetching example packages as well. -/// /// If [streamOutput] is false, output will only be printed if the command /// fails. Future runPubGet( RepositoryPackage package, ProcessRunner processRunner, Platform platform, { - bool alwaysUseFlutter = false, bool streamOutput = true, }) async { - // Running `dart pub get` on a Flutter package can fail if a non-Flutter Dart - // is first in the path, so use `flutter pub get` for any Flutter package. - final bool useFlutter = alwaysUseFlutter || package.requiresFlutter(); - final command = useFlutter - ? (platform.isWindows ? 'flutter.bat' : 'flutter') - : 'dart'; - final args = ['pub', 'get']; + return runPubCommand( + ['get'], + package, + processRunner, + platform, + streamOutput: streamOutput, + ); +} +/// Runs a pub command with the given arguments in [package], +/// using either 'dart' or 'flutter' depending on the package type. +/// +/// If [streamOutput] is false, output will only be printed if the command +/// fails. +Future runPubCommand( + List commandArgs, + RepositoryPackage package, + ProcessRunner processRunner, + Platform platform, { + bool streamOutput = true, + String? dartSdkPathOverride, +}) async { + final String command = _pubCommand( + package, + platform, + dartSdkPathOverride: dartSdkPathOverride, + ); + final args = ['pub', ...commandArgs]; final int exitCode; if (streamOutput) { exitCode = await processRunner.runAndStream( @@ -53,3 +68,32 @@ Future runPubGet( } return exitCode == 0; } + +/// Starts a pub command with the given arguments in [package], +/// using either 'dart' or 'flutter' depending on the package type, and returns +/// a process that can be used to wait for completion and stream output. +/// +/// If no output capturing is necessary, prefer [runPubCommand]. +Future startPubCommand( + List commandArgs, + RepositoryPackage package, + ProcessRunner processRunner, + Platform platform, +) async { + return processRunner.start(_pubCommand(package, platform), [ + 'pub', + ...commandArgs, + ], workingDirectory: package.directory); +} + +String _pubCommand( + RepositoryPackage package, + Platform platform, { + String? dartSdkPathOverride, +}) { + // Running `dart pub get` on a Flutter package can fail if a non-Flutter Dart + // is first in the path, so use `flutter pub get` for any Flutter package. + return package.requiresFlutter() + ? (platform.isWindows ? 'flutter.bat' : 'flutter') + : (dartSdkPathOverride ?? 'dart'); +} diff --git a/script/tool/lib/src/publish_check_command.dart b/script/tool/lib/src/publish_check_command.dart index dbb6a0711e83..2e28b39e572e 100644 --- a/script/tool/lib/src/publish_check_command.dart +++ b/script/tool/lib/src/publish_check_command.dart @@ -163,10 +163,11 @@ class PublishCheckCommand extends PackageLoopingCommand { await _fetchExampleDeps(package); print('Running pub publish --dry-run:'); - final io.Process process = await processRunner.start( - flutterCommand, - ['pub', 'publish', '--', '--dry-run'], - workingDirectory: package.directory, + final io.Process process = await startPubCommand( + ['publish', '--dry-run'], + package, + processRunner, + platform, ); final outputBuffer = StringBuffer(); diff --git a/script/tool/lib/src/publish_command.dart b/script/tool/lib/src/publish_command.dart index 41d982d4d9d3..54b5a4dda1da 100644 --- a/script/tool/lib/src/publish_command.dart +++ b/script/tool/lib/src/publish_command.dart @@ -477,10 +477,11 @@ Safe to ignore if the package is deleted in this commit. _ensureValidPubCredential(); } - final io.Process publish = await processRunner.start( - flutterCommand, - ['pub', 'publish', ..._publishFlags], - workingDirectory: package.directory, + final io.Process publish = await startPubCommand( + ['publish', ..._publishFlags], + package, + processRunner, + platform, ); publish.stdout.transform(utf8.decoder).listen((String data) => print(data)); publish.stderr.transform(utf8.decoder).listen((String data) => print(data)); diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index 1752a98250f0..ed259156675f 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_plugin_tools description: Productivity and CI utils for Flutter team package repositories. repository: https://github.com/flutter/packages/tree/main/script/tool -version: 0.14.1 +version: 0.14.2 dependencies: args: ^2.1.0 diff --git a/script/tool/test/analyze_command_test.dart b/script/tool/test/analyze_command_test.dart index 9e0b2d6a0752..1f5da3ec3d32 100644 --- a/script/tool/test/analyze_command_test.dart +++ b/script/tool/test/analyze_command_test.dart @@ -136,6 +136,8 @@ void main() { group('dart analyze', () { test('analyzes all packages', () async { + // Create a non-Flutter Dart package and a Flutter plugin to make sure + // the right command is used for each. final RepositoryPackage package1 = createFakePackage('a', packagesDir); final RepositoryPackage plugin2 = createFakePlugin('b', packagesDir); @@ -144,7 +146,7 @@ void main() { expect( processRunner.recordedCalls, orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], package1.path), + ProcessCall('dart', const ['pub', 'get'], package1.path), ProcessCall('dart', const [ 'analyze', '--fatal-infos', @@ -158,7 +160,7 @@ void main() { ); }); - test('skips flutter pub get for examples', () async { + test('skips pub get for examples', () async { final RepositoryPackage plugin1 = createFakePlugin('a', packagesDir); await runCapturingPrint(runner, ['analyze']); @@ -175,7 +177,7 @@ void main() { ); }); - test('runs flutter pub get for non-example subpackages', () async { + test('runs pub get for non-example subpackages', () async { final RepositoryPackage mainPackage = createFakePackage('a', packagesDir); final Directory otherPackagesDir = mainPackage.directory.childDirectory( 'other_packages', @@ -194,18 +196,9 @@ void main() { expect( processRunner.recordedCalls, orderedEquals([ - ProcessCall('flutter', const [ - 'pub', - 'get', - ], mainPackage.path), - ProcessCall('flutter', const [ - 'pub', - 'get', - ], subpackage1.path), - ProcessCall('flutter', const [ - 'pub', - 'get', - ], subpackage2.path), + ProcessCall('dart', const ['pub', 'get'], mainPackage.path), + ProcessCall('dart', const ['pub', 'get'], subpackage1.path), + ProcessCall('dart', const ['pub', 'get'], subpackage2.path), ProcessCall('dart', const [ 'analyze', '--fatal-infos', @@ -225,7 +218,7 @@ void main() { expect( processRunner.recordedCalls, orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], package.path), + ProcessCall('dart', const ['pub', 'get'], package.path), ProcessCall('dart', const [ 'analyze', '--fatal-infos', @@ -255,7 +248,7 @@ void main() { }); test( - 'does not run flutter pub get for non-example subpackages with --lib-only', + 'does not run pub get for non-example subpackages with --lib-only', () async { final RepositoryPackage mainPackage = createFakePackage( 'a', @@ -272,10 +265,7 @@ void main() { expect( processRunner.recordedCalls, orderedEquals([ - ProcessCall('flutter', const [ - 'pub', - 'get', - ], mainPackage.path), + ProcessCall('dart', const ['pub', 'get'], mainPackage.path), ProcessCall('dart', const [ 'analyze', '--fatal-infos', diff --git a/script/tool/test/common/package_looping_command_test.dart b/script/tool/test/common/package_looping_command_test.dart index 1589e748b435..49b1efd5a275 100644 --- a/script/tool/test/common/package_looping_command_test.dart +++ b/script/tool/test/common/package_looping_command_test.dart @@ -501,7 +501,51 @@ skip/b ); }); - test('skips unsupported Dart versions when requested', () async { + test( + 'skips unsupported Dart versions when requested implicitly by Flutter version', + () async { + final RepositoryPackage excluded = createFakePackage( + 'excluded_package', + packagesDir, + dartConstraint: '>=2.18.0 <4.0.0', + ); + final RepositoryPackage included = createFakePackage( + 'a_package', + packagesDir, + ); + + final TestPackageLoopingCommand command = createTestCommand( + packageLoopingType: PackageLoopingType.includeAllSubpackages, + hasLongOutput: false, + ); + final List output = await runCommand( + command, + arguments: [ + '--skip-if-not-supporting-flutter-version=3.0.0', // Flutter 3.0.0 -> Dart 2.17.0 + ], + ); + + expect( + command.checkedPackages, + unorderedEquals([ + included.path, + getExampleDir(included).path, + ]), + ); + expect(command.checkedPackages, isNot(contains(excluded.path))); + + expect( + output, + containsAllInOrder([ + '${_startHeadingColor}Running for a_package...$_endColor', + '${_startHeadingColor}Running for excluded_package...$_endColor', + '$_startSkipColor SKIPPING: Does not support Dart 2.17.0$_endColor', + ]), + ); + }, + ); + + test('skips unsupported Dart versions when requested explicitly', () async { final RepositoryPackage excluded = createFakePackage( 'excluded_package', packagesDir, @@ -518,9 +562,7 @@ skip/b ); final List output = await runCommand( command, - arguments: [ - '--skip-if-not-supporting-flutter-version=3.0.0', // Flutter 3.0.0 -> Dart 2.17.0 - ], + arguments: ['--skip-if-not-supporting-dart-version=2.17.0'], ); expect( diff --git a/script/tool/test/common/pub_utils_test.dart b/script/tool/test/common/pub_utils_test.dart index d18b1290a239..2dcc3627c9b5 100644 --- a/script/tool/test/common/pub_utils_test.dart +++ b/script/tool/test/common/pub_utils_test.dart @@ -53,23 +53,6 @@ void main() { ); }); - test('runs with Flutter for a Dart package when requested', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', - packagesDir, - ); - final platform = MockPlatform(); - - await runPubGet(package, processRunner, platform, alwaysUseFlutter: true); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], package.path), - ]), - ); - }); - test('uses the correct Flutter command on Windows', () async { final RepositoryPackage package = createFakePackage( 'a_package', diff --git a/script/tool/test/publish_check_command_test.dart b/script/tool/test/publish_check_command_test.dart index 7b1b265e63a4..a97737517615 100644 --- a/script/tool/test/publish_check_command_test.dart +++ b/script/tool/test/publish_check_command_test.dart @@ -71,13 +71,11 @@ void main() { ProcessCall('flutter', const [ 'pub', 'publish', - '--', '--dry-run', ], plugin1.path), ProcessCall('flutter', const [ 'pub', 'publish', - '--', '--dry-run', ], plugin2.path), ]), @@ -117,14 +115,12 @@ void main() { ProcessCall('flutter', const [ 'pub', 'publish', - '--', '--dry-run', ], plugin1.path), // plugin2 has no examples, so there's no extra 'dart pub get' calls. ProcessCall('flutter', const [ 'pub', 'publish', - '--', '--dry-run', ], plugin2.path), ]), @@ -329,7 +325,8 @@ void main() { runner = configureRunner(httpClient: mockClient); - processRunner.mockProcessesForExecutable['flutter'] = [ + processRunner.mockProcessesForExecutable['dart'] = [ + FakeProcessInfo(MockProcess(), ['pub', 'get']), FakeProcessInfo( MockProcess(exitCode: 1, stdout: 'Some error from pub'), ['pub', 'publish'], @@ -355,10 +352,9 @@ void main() { expect( processRunner.recordedCalls, contains( - ProcessCall('flutter', const [ + ProcessCall('dart', const [ 'pub', 'publish', - '--', '--dry-run', ], package.path), ), @@ -426,10 +422,9 @@ void main() { expect( processRunner.recordedCalls, contains( - ProcessCall('flutter', const [ + ProcessCall('dart', const [ 'pub', 'publish', - '--', '--dry-run', ], package.path), ), @@ -496,10 +491,9 @@ void main() { 'run', 'tool/pre_publish.dart', ], package.directory.path), - ProcessCall('flutter', const [ + ProcessCall('dart', const [ 'pub', 'publish', - '--', '--dry-run', ], package.directory.path), ]), diff --git a/script/tool/test/publish_command_test.dart b/script/tool/test/publish_command_test.dart index 0438b8cffeda..f32119b4f230 100644 --- a/script/tool/test/publish_command_test.dart +++ b/script/tool/test/publish_command_test.dart @@ -285,6 +285,29 @@ void main() { ); }); + test('uses dart rather than flutter for non-Flutter packages', () async { + final RepositoryPackage package = createFakePackage( + 'foo', + packagesDir, + examples: [], + ); + + await runCapturingPrint(commandRunner, [ + 'publish', + '--packages=foo', + ]); + + expect( + processRunner.recordedCalls, + contains( + ProcessCall('dart', const [ + 'pub', + 'publish', + ], package.directory.path), + ), + ); + }); + test('forwards --pub-publish-flags to pub publish', () async { final RepositoryPackage plugin = createFakePlugin( 'foo',