[tool/ci] Add iOS/macOS and Dart support to `fetch-deps` (#4562)
Adds `fetch-deps` support for:
- iOS/macOS dependencies, using `pod install`
- Dart package dependencies, using `pub get`
To make avoid doing extra work in the Dart dependencies step when using this with `*_platform_tests` CI, also adds flags for all of the other platforms, and adds a flag that allows skipping Dart dependencies for any package that doesn't have an example supporting any requested platform. This means that we can pass, e.g., `--windows --supporting-target-platforms-only` to only fetch Dart packages for packages with examples that will be build during the build-and-drive Windows tests.
Adds this as a new step in every platform tests CI task, and in the standard analyze step, so that we will pre-fetch Dart packages (and for iOS/macOS, pods). This won't yet fully eliminate later network access (see https://github.com/flutter/flutter/issues/131204), but will give us early warning on any major failures, such as pub being entirely unreachable from the bots.
- These are marked as an infrastructure step; we'll have to see if this ends up being confusing in practice. If `pub` resolution fails for legitimate reasons, such as a PR that tries to require a version of a package that doesn't exist or that has conflicts, this will cause a failure that is marked as infra. My assumption is that the much more common case is going to be that it is actually an infra failure.
Fixes https://github.com/flutter/flutter/issues/130280
diff --git a/.ci/targets/analyze.yaml b/.ci/targets/analyze.yaml
index 793ac03..2262e6a 100644
--- a/.ci/targets/analyze.yaml
+++ b/.ci/targets/analyze.yaml
@@ -4,6 +4,10 @@
infra_step: true # Note infra steps failing prevents "always" from running.
- name: analyze repo tools
script: .ci/scripts/analyze_repo_tools.sh
+ - name: download Dart deps
+ script: script/tool_runner.sh
+ args: ["fetch-deps"]
+ infra_step: true
- name: analyze
script: script/tool_runner.sh
# DO NOT change the custom-analysis argument here without changing the Dart repo.
diff --git a/.ci/targets/android_platform_tests.yaml b/.ci/targets/android_platform_tests.yaml
index 7ee1767..eea5063 100644
--- a/.ci/targets/android_platform_tests.yaml
+++ b/.ci/targets/android_platform_tests.yaml
@@ -2,10 +2,10 @@
- name: prepare tool
script: .ci/scripts/prepare_tool.sh
infra_step: true # Note infra steps failing prevents "always" from running.
- - name: download android deps
+ - name: download Dart and Android deps
script: script/tool_runner.sh
infra_step: true
- args: ["fetch-deps"]
+ args: ["fetch-deps", "--android", "--supporting-target-platforms-only"]
- name: build examples
script: script/tool_runner.sh
args: ["build-examples", "--apk"]
diff --git a/.ci/targets/ios_platform_tests.yaml b/.ci/targets/ios_platform_tests.yaml
index 97b92d3..212c8c9 100644
--- a/.ci/targets/ios_platform_tests.yaml
+++ b/.ci/targets/ios_platform_tests.yaml
@@ -5,6 +5,10 @@
- name: create simulator
script: .ci/scripts/create_simulator.sh
infra_step: true # Note infra steps failing prevents "always" from running.
+ - name: download Dart and iOS deps
+ script: script/tool_runner.sh
+ args: ["fetch-deps", "--ios", "--supporting-target-platforms-only"]
+ infra_step: true
- name: build examples
script: script/tool_runner.sh
args: ["build-examples", "--ios"]
diff --git a/.ci/targets/linux_platform_tests.yaml b/.ci/targets/linux_platform_tests.yaml
index 3455958..6d13b7d 100644
--- a/.ci/targets/linux_platform_tests.yaml
+++ b/.ci/targets/linux_platform_tests.yaml
@@ -3,6 +3,11 @@
script: .ci/scripts/prepare_tool.sh
- name: set default apps
script: .ci/scripts/set_default_linux_apps.sh
+ infra_step: true
+ - name: download Dart deps
+ script: script/tool_runner.sh
+ args: ["fetch-deps", "--linux", "--supporting-target-platforms-only"]
+ infra_step: true
- name: build examples
script: script/tool_runner.sh
args: ["build-examples", "--linux"]
diff --git a/.ci/targets/macos_platform_tests.yaml b/.ci/targets/macos_platform_tests.yaml
index 92b937e..512e1c9 100644
--- a/.ci/targets/macos_platform_tests.yaml
+++ b/.ci/targets/macos_platform_tests.yaml
@@ -2,6 +2,10 @@
- name: prepare tool
script: .ci/scripts/prepare_tool.sh
infra_step: true # Note infra steps failing prevents "always" from running.
+ - name: download Dart and macOS deps
+ script: script/tool_runner.sh
+ args: ["fetch-deps", "--macos", "--supporting-target-platforms-only"]
+ infra_step: true
- name: build examples
script: script/tool_runner.sh
args: ["build-examples", "--macos"]
diff --git a/.ci/targets/web_platform_tests.yaml b/.ci/targets/web_platform_tests.yaml
index acfb9cd..2d28b45 100644
--- a/.ci/targets/web_platform_tests.yaml
+++ b/.ci/targets/web_platform_tests.yaml
@@ -2,6 +2,10 @@
- name: prepare tool
script: .ci/scripts/prepare_tool.sh
infra_step: true # Note infra steps failing prevents "always" from running.
+ - name: download Dart deps
+ script: script/tool_runner.sh
+ args: ["fetch-deps", "--web", "--supporting-target-platforms-only"]
+ infra_step: true
- name: build examples
script: script/tool_runner.sh
args: ["build-examples", "--web"]
diff --git a/.ci/targets/windows_build_and_platform_tests.yaml b/.ci/targets/windows_build_and_platform_tests.yaml
index 4bd73f9..da1dfa8 100644
--- a/.ci/targets/windows_build_and_platform_tests.yaml
+++ b/.ci/targets/windows_build_and_platform_tests.yaml
@@ -2,6 +2,10 @@
- name: prepare tool
script: .ci/scripts/prepare_tool.sh
infra_step: true # Note infra steps failing prevents "always" from running.
+ - name: download Dart deps
+ script: script/tool_runner.sh
+ args: ["fetch-deps", "--windows", "--supporting-target-platforms-only"]
+ infra_step: true
- name: build examples (Win32)
script: .ci/scripts/build_examples_win32.sh
- name: native unit tests (Win32)
diff --git a/script/tool/lib/src/build_examples_command.dart b/script/tool/lib/src/build_examples_command.dart
index 728ac11..0f78d39 100644
--- a/script/tool/lib/src/build_examples_command.dart
+++ b/script/tool/lib/src/build_examples_command.dart
@@ -176,9 +176,8 @@
// supported platforms. For packages, just log and skip any requested
// platform that a package doesn't have set up.
if (!isPlugin &&
- !example.directory
- .childDirectory(platform.flutterPlatformDirectory)
- .existsSync()) {
+ !example.appSupportsPlatform(
+ getPlatformByName(platform.pluginPlatform))) {
print('Skipping ${platform.label} for $packageName; not supported.');
continue;
}
@@ -304,11 +303,6 @@
/// The `flutter build` build type.
final String flutterBuildType;
- /// The Flutter platform directory name.
- // In practice, this is the same as the plugin platform key for all platforms.
- // If that changes, this can be adjusted.
- String get flutterPlatformDirectory => pluginPlatform;
-
/// Any extra flags to pass to `flutter build`.
final List<String> extraBuildFlags;
}
diff --git a/script/tool/lib/src/common/core.dart b/script/tool/lib/src/common/core.dart
index 96224f5..4cf62b2 100644
--- a/script/tool/lib/src/common/core.dart
+++ b/script/tool/lib/src/common/core.dart
@@ -37,6 +37,25 @@
// ignore: public_member_api_docs
enum FlutterPlatform { android, ios, linux, macos, web, windows }
+const Map<String, FlutterPlatform> _platformByName = <String, FlutterPlatform>{
+ platformAndroid: FlutterPlatform.android,
+ platformIOS: FlutterPlatform.ios,
+ platformLinux: FlutterPlatform.linux,
+ platformMacOS: FlutterPlatform.macos,
+ platformWeb: FlutterPlatform.web,
+ platformWindows: FlutterPlatform.windows,
+};
+
+/// Maps from a platform name (e.g., flag or platform directory) to the
+/// corresponding platform enum.
+FlutterPlatform getPlatformByName(String name) {
+ final FlutterPlatform? platform = _platformByName[name];
+ if (platform == null) {
+ throw ArgumentError('Invalid platform: $name');
+ }
+ return platform;
+}
+
// Flutter->Dart SDK version mapping. Any time a command fails to look up a
// corresponding version, this map should be updated.
final Map<Version, Version> _dartSdkForFlutterSdk = <Version, Version>{
diff --git a/script/tool/lib/src/common/process_runner.dart b/script/tool/lib/src/common/process_runner.dart
index 7556b55..8ac98b8 100644
--- a/script/tool/lib/src/common/process_runner.dart
+++ b/script/tool/lib/src/common/process_runner.dart
@@ -30,12 +30,13 @@
String executable,
List<String> args, {
Directory? workingDir,
+ Map<String, String>? environment,
bool exitOnError = false,
}) async {
print(
'Running command: "$executable ${args.join(' ')}" in ${workingDir?.path ?? io.Directory.current.path}');
final io.Process process = await io.Process.start(executable, args,
- workingDirectory: workingDir?.path);
+ workingDirectory: workingDir?.path, environment: environment);
await Future.wait(<Future<dynamic>>[
io.stdout.addStream(process.stdout),
io.stderr.addStream(process.stderr),
@@ -62,14 +63,19 @@
/// Defaults to `false`
///
/// Returns the [io.ProcessResult] of the [executable].
- Future<io.ProcessResult> run(String executable, List<String> args,
- {Directory? workingDir,
- bool exitOnError = false,
- bool logOnError = false,
- Encoding stdoutEncoding = io.systemEncoding,
- Encoding stderrEncoding = io.systemEncoding}) async {
+ Future<io.ProcessResult> run(
+ String executable,
+ List<String> args, {
+ Directory? workingDir,
+ Map<String, String>? environment,
+ bool exitOnError = false,
+ bool logOnError = false,
+ Encoding stdoutEncoding = io.systemEncoding,
+ Encoding stderrEncoding = io.systemEncoding,
+ }) async {
final io.ProcessResult result = await io.Process.run(executable, args,
workingDirectory: workingDir?.path,
+ environment: environment,
stdoutEncoding: stdoutEncoding,
stderrEncoding: stderrEncoding);
if (result.exitCode != 0) {
diff --git a/script/tool/lib/src/common/repository_package.dart b/script/tool/lib/src/common/repository_package.dart
index 2a824f5..345ace5 100644
--- a/script/tool/lib/src/common/repository_package.dart
+++ b/script/tool/lib/src/common/repository_package.dart
@@ -92,6 +92,16 @@
return directory.childDirectory(directoryName);
}
+ /// Returns true if the package is an app that supports [platform].
+ ///
+ /// The "app" prefix on this method is because this currently only works
+ /// for app packages (e.g., examples).
+ // TODO(stuartmorgan): Add support for non-app packages, by parsing the
+ // pubspec for `flutter:platform:` or `platform:` sections.
+ bool appSupportsPlatform(FlutterPlatform platform) {
+ return platformDirectory(platform).existsSync();
+ }
+
late final Pubspec _parsedPubspec =
Pubspec.parse(pubspecFile.readAsStringSync());
diff --git a/script/tool/lib/src/drive_examples_command.dart b/script/tool/lib/src/drive_examples_command.dart
index ac3e6f4..535ea1e 100644
--- a/script/tool/lib/src/drive_examples_command.dart
+++ b/script/tool/lib/src/drive_examples_command.dart
@@ -254,7 +254,7 @@
for (final MapEntry<String, List<String>> entry
in _targetDeviceFlags.entries) {
final String platform = entry.key;
- if (example.directory.childDirectory(platform).existsSync()) {
+ if (example.appSupportsPlatform(getPlatformByName(platform))) {
deviceFlags.addAll(entry.value);
} else {
final String exampleName =
diff --git a/script/tool/lib/src/fetch_deps_command.dart b/script/tool/lib/src/fetch_deps_command.dart
index ce70b41..2f763a0 100644
--- a/script/tool/lib/src/fetch_deps_command.dart
+++ b/script/tool/lib/src/fetch_deps_command.dart
@@ -2,6 +2,7 @@
// 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 'common/core.dart';
import 'common/gradle.dart';
@@ -10,12 +11,15 @@
import 'common/plugin_utils.dart';
import 'common/repository_package.dart';
-/// Download dependencies for the following platforms {android}.
+const int _exitPrecacheFailed = 3;
+const int _exitNothingRequested = 4;
+
+/// Download dependencies, both Dart and native.
///
/// Specficially each platform runs:
/// Android: 'gradlew dependencies'.
-/// Dart: TBD (flutter/flutter/issues/130279)
-/// iOS: TBD (flutter/flutter/issues/130280)
+/// Dart: 'flutter pub get'.
+/// iOS/macOS: 'pod install'.
///
/// See https://docs.gradle.org/6.4/userguide/core_dependency_management.html#sec:dependency-mgmt-in-gradle.
class FetchDepsCommand extends PackageLoopingCommand {
@@ -24,25 +28,153 @@
super.packagesDir, {
super.processRunner,
super.platform,
- });
+ }) {
+ argParser.addFlag(_dartFlag, defaultsTo: true, help: 'Run "pub get"');
+ argParser.addFlag(_supportingTargetPlatformsOnlyFlag,
+ help: 'Restricts "pub get" runs to packages that have at least one '
+ 'example supporting at least one of the platform flags passed.\n'
+ 'If no platform flags are passed, this will exclude all packages.');
+ argParser.addFlag(platformAndroid,
+ help: 'Run "gradlew dependencies" for Android plugins.\n'
+ 'Include packages with Android examples when used with '
+ '--$_supportingTargetPlatformsOnlyFlag');
+ argParser.addFlag(platformIOS,
+ help: 'Run "pod install" for iOS plugins.\n'
+ 'Include packages with iOS examples when used with '
+ '--$_supportingTargetPlatformsOnlyFlag');
+ argParser.addFlag(platformLinux,
+ help: 'Include packages with Linux examples when used with '
+ '--$_supportingTargetPlatformsOnlyFlag');
+ argParser.addFlag(platformMacOS,
+ help: 'Run "pod install" for macOS plugins.\n'
+ 'Include packages with macOS examples when used with '
+ '--$_supportingTargetPlatformsOnlyFlag');
+ argParser.addFlag(platformWeb,
+ help: 'Include packages with Web examples when used with '
+ '--$_supportingTargetPlatformsOnlyFlag');
+ argParser.addFlag(platformWindows,
+ help: 'Include packages with Windows examples when used with '
+ '--$_supportingTargetPlatformsOnlyFlag');
+ }
+
+ static const String _dartFlag = 'dart';
+ static const String _supportingTargetPlatformsOnlyFlag =
+ 'supporting-target-platforms-only';
+
+ static const Iterable<String> _platforms = <String>[
+ platformAndroid,
+ platformIOS,
+ platformLinux,
+ platformMacOS,
+ platformWeb,
+ platformWindows,
+ ];
@override
final String name = 'fetch-deps';
@override
- final String description = 'Fetches dependencies for plugins.\n'
- 'Runs "gradlew dependencies" on Android plugins.\n'
- 'Dart see flutter/flutter/issues/130279\n'
- 'iOS plugins see flutter/flutter/issues/130280\n'
- '\n'
- 'Requires the examples to have been built at least once before running.';
+ final String description = 'Fetches dependencies for packages';
+
+ @override
+ Future<void> initializeRun() async {
+ // `pod install` requires having the platform artifacts precached. See
+ // https://github.com/flutter/flutter/blob/fb7a763c640d247d090cbb373e4b3a0459ac171b/packages/flutter_tools/bin/podhelper.rb#L47
+ // https://github.com/flutter/flutter/blob/fb7a763c640d247d090cbb373e4b3a0459ac171b/packages/flutter_tools/bin/podhelper.rb#L130
+ if (getBoolArg(platformIOS)) {
+ final int exitCode = await processRunner.runAndStream(
+ flutterCommand,
+ <String>['precache', '--ios'],
+ );
+ if (exitCode != 0) {
+ throw ToolExit(_exitPrecacheFailed);
+ }
+ }
+ if (getBoolArg(platformMacOS)) {
+ final int exitCode = await processRunner.runAndStream(
+ flutterCommand,
+ <String>['precache', '--macos'],
+ );
+ if (exitCode != 0) {
+ throw ToolExit(_exitPrecacheFailed);
+ }
+ }
+ }
@override
Future<PackageResult> runForPackage(RepositoryPackage package) async {
+ bool fetchedDeps = false;
+ final List<String> skips = <String>[];
+ if (getBoolArg(_dartFlag)) {
+ final bool filterPlatforms =
+ getBoolArg(_supportingTargetPlatformsOnlyFlag);
+ if (!filterPlatforms || _hasExampleSupportingRequestedPlatform(package)) {
+ fetchedDeps = true;
+ if (!await _fetchDartPackages(package)) {
+ // If Dart-level depenendencies fail, fail immediately since the
+ // native dependencies won't be useful.
+ return PackageResult.fail(<String>['Failed to "pub get".']);
+ }
+ } else {
+ skips.add('Skipping Dart dependencies; no examples support requested '
+ 'platforms.');
+ }
+ }
+
+ final List<String> errors = <String>[];
+ for (final FlutterPlatform platform in _targetPlatforms) {
+ final PackageResult result;
+ switch (platform) {
+ case FlutterPlatform.android:
+ result = await _fetchAndroidDeps(package);
+ break;
+ case FlutterPlatform.ios:
+ result = await _fetchDarwinDeps(package, platformIOS);
+ break;
+ case FlutterPlatform.macos:
+ result = await _fetchDarwinDeps(package, platformMacOS);
+ break;
+ case FlutterPlatform.linux:
+ case FlutterPlatform.web:
+ case FlutterPlatform.windows:
+ // No native dependency handling yet.
+ result = PackageResult.skip('Nothing to do for $platform.');
+ break;
+ }
+ switch (result.state) {
+ case RunState.succeeded:
+ fetchedDeps = true;
+ break;
+ case RunState.skipped:
+ skips.add(result.details.first);
+ break;
+ case RunState.failed:
+ errors.addAll(result.details);
+ break;
+ case RunState.excluded:
+ throw StateError('Unreachable');
+ }
+ }
+
+ if (errors.isNotEmpty) {
+ return PackageResult.fail(errors);
+ }
+ if (fetchedDeps) {
+ return PackageResult.success();
+ }
+ if (skips.isNotEmpty) {
+ return PackageResult.skip(<String>['', ...skips].join('\n- '));
+ }
+
+ printError('At least one type of dependency must be requested');
+ throw ToolExit(_exitNothingRequested);
+ }
+
+ Future<PackageResult> _fetchAndroidDeps(RepositoryPackage package) async {
if (!pluginSupportsPlatform(platformAndroid, package,
requiredMode: PlatformSupport.inline)) {
return PackageResult.skip(
- 'Plugin does not have an Android implementation.');
+ 'Package does not have native Android dependencies.');
}
for (final RepositoryPackage example in package.getExamples()) {
@@ -63,7 +195,8 @@
final String packageName = package.directory.basename;
- final int exitCode = await gradleProject.runCommand('$packageName:dependencies');
+ final int exitCode =
+ await gradleProject.runCommand('$packageName:dependencies');
if (exitCode != 0) {
return PackageResult.fail();
}
@@ -71,4 +204,78 @@
return PackageResult.success();
}
+
+ Future<PackageResult> _fetchDarwinDeps(
+ RepositoryPackage package, final String platform) async {
+ if (!pluginSupportsPlatform(platform, package,
+ requiredMode: PlatformSupport.inline)) {
+ // Convert from the flag (lower case ios/macos) to the actual name.
+ final String displayPlatform = platform.replaceFirst('os', 'OS');
+ return PackageResult.skip(
+ 'Package does not have native $displayPlatform dependencies.');
+ }
+
+ for (final RepositoryPackage example in package.getExamples()) {
+ final Directory platformDir =
+ example.platformDirectory(getPlatformByName(platform));
+
+ // Running `pod install` requires `flutter pub get` or `flutter build` to
+ // have been run at some point to create the necessary native build files.
+ // See https://github.com/flutter/flutter/blob/fb7a763c640d247d090cbb373e4b3a0459ac171b/packages/flutter_tools/templates/cocoapods/Podfile-macos#L13-L15
+ // and https://github.com/flutter/flutter/blob/fb7a763c640d247d090cbb373e4b3a0459ac171b/packages/flutter_tools/templates/cocoapods/Podfile-ios-swift#L14-L16
+ final File generatedXCConfig = platform == platformMacOS
+ ? platformDir
+ .childDirectory('Flutter')
+ .childDirectory('ephemeral')
+ .childFile('Flutter-Generated.xcconfig')
+ : platformDir
+ .childDirectory('Flutter')
+ .childFile('Generated.xcconfig');
+ if (!generatedXCConfig.existsSync()) {
+ final int exitCode = await processRunner.runAndStream(
+ flutterCommand,
+ <String>['pub', 'get'],
+ workingDir: example.directory,
+ );
+ if (exitCode != 0) {
+ printError('Unable to prepare native project files.');
+ return PackageResult.fail(<String>['Unable to configure project.']);
+ }
+ }
+
+ final int exitCode = await processRunner.runAndStream(
+ 'pod',
+ <String>['install'],
+ workingDir: platformDir,
+ environment: <String, String>{
+ 'LANG': 'en_US.UTF-8',
+ },
+ );
+ if (exitCode != 0) {
+ printError('Unable to "pod install"');
+ return PackageResult.fail(<String>['Unable to "pod install"']);
+ }
+ }
+
+ return PackageResult.success();
+ }
+
+ Future<bool> _fetchDartPackages(RepositoryPackage package) async {
+ final String command = package.requiresFlutter() ? flutterCommand : 'dart';
+ final int exitCode = await processRunner.runAndStream(
+ command, <String>['pub', 'get'],
+ workingDir: package.directory);
+ return exitCode == 0;
+ }
+
+ bool _hasExampleSupportingRequestedPlatform(RepositoryPackage package) {
+ return package.getExamples().any((RepositoryPackage example) {
+ return _targetPlatforms.any(
+ (FlutterPlatform platform) => example.appSupportsPlatform(platform));
+ });
+ }
+
+ Iterable<FlutterPlatform> get _targetPlatforms => _platforms
+ .where((String platform) => getBoolArg(platform))
+ .map((String platformName) => getPlatformByName(platformName));
}
diff --git a/script/tool/test/fetch_deps_command_test.dart b/script/tool/test/fetch_deps_command_test.dart
index baff6f6..ab4cd3f 100644
--- a/script/tool/test/fetch_deps_command_test.dart
+++ b/script/tool/test/fetch_deps_command_test.dart
@@ -36,7 +36,142 @@
CommandRunner<void>('fetch_deps_test', 'Test for $FetchDepsCommand');
runner.addCommand(command);
});
+
+ group('dart', () {
+ test('runs pub get', () async {
+ final RepositoryPackage plugin = createFakePlugin(
+ 'plugin1', packagesDir, platformSupport: <String, PlatformDetails>{
+ platformIOS: const PlatformDetails(PlatformSupport.inline)
+ });
+
+ final List<String> output =
+ await runCapturingPrint(runner, <String>['fetch-deps']);
+
+ expect(
+ processRunner.recordedCalls,
+ orderedEquals(<ProcessCall>[
+ ProcessCall(
+ 'flutter',
+ const <String>['pub', 'get'],
+ plugin.directory.path,
+ ),
+ ]),
+ );
+
+ expect(
+ output,
+ containsAllInOrder(<Matcher>[
+ contains('Running for plugin1'),
+ contains('No issues found!'),
+ ]));
+ });
+
+ test('fails if pub get fails', () async {
+ createFakePlugin('plugin1', packagesDir,
+ platformSupport: <String, PlatformDetails>{
+ platformIOS: const PlatformDetails(PlatformSupport.inline)
+ });
+
+ processRunner
+ .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
+ <FakeProcessInfo>[
+ FakeProcessInfo(MockProcess(exitCode: 1)),
+ ];
+
+ Error? commandError;
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps'], errorHandler: (Error e) {
+ commandError = e;
+ });
+
+ expect(commandError, isA<ToolExit>());
+ expect(
+ output,
+ containsAllInOrder(
+ <Matcher>[
+ contains('Failed to "pub get"'),
+ ],
+ ));
+ });
+
+ test('skips unsupported packages when any platforms are passed',
+ () async {
+ final RepositoryPackage packageWithBoth = createFakePackage(
+ 'supports_both', packagesDir, extraFiles: <String>[
+ 'example/linux/placeholder',
+ 'example/windows/placeholder'
+ ]);
+ final RepositoryPackage packageWithOne = createFakePackage(
+ 'supports_one', packagesDir,
+ extraFiles: <String>['example/linux/placeholder']);
+ createFakePackage('supports_neither', packagesDir);
+
+ await runCapturingPrint(runner, <String>[
+ 'fetch-deps',
+ '--linux',
+ '--windows',
+ '--supporting-target-platforms-only'
+ ]);
+
+ expect(
+ processRunner.recordedCalls,
+ orderedEquals(<ProcessCall>[
+ ProcessCall(
+ 'dart',
+ const <String>['pub', 'get'],
+ packageWithBoth.path,
+ ),
+ ProcessCall(
+ 'dart',
+ const <String>['pub', 'get'],
+ packageWithOne.path,
+ ),
+ ]),
+ );
+ });
+ });
+
group('android', () {
+ test('runs pub get before gradlew dependencies', () async {
+ final RepositoryPackage plugin =
+ createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
+ 'example/android/gradlew',
+ ], platformSupport: <String, PlatformDetails>{
+ platformAndroid: const PlatformDetails(PlatformSupport.inline)
+ });
+
+ final Directory androidDir = plugin
+ .getExamples()
+ .first
+ .platformDirectory(FlutterPlatform.android);
+
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--android']);
+
+ expect(
+ processRunner.recordedCalls,
+ orderedEquals(<ProcessCall>[
+ ProcessCall(
+ 'flutter',
+ const <String>['pub', 'get'],
+ plugin.directory.path,
+ ),
+ ProcessCall(
+ androidDir.childFile('gradlew').path,
+ const <String>['plugin1:dependencies'],
+ androidDir.path,
+ ),
+ ]),
+ );
+
+ expect(
+ output,
+ containsAllInOrder(<Matcher>[
+ contains('Running for plugin1'),
+ contains('No issues found!'),
+ ]));
+ });
+
test('runs gradlew dependencies', () async {
final RepositoryPackage plugin =
createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
@@ -50,8 +185,8 @@
.first
.platformDirectory(FlutterPlatform.android);
- final List<String> output =
- await runCapturingPrint(runner, <String>['fetch-deps']);
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--android']);
expect(
processRunner.recordedCalls,
@@ -89,8 +224,8 @@
(RepositoryPackage example) =>
example.platformDirectory(FlutterPlatform.android));
- final List<String> output =
- await runCapturingPrint(runner, <String>['fetch-deps']);
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--android']);
expect(
processRunner.recordedCalls,
@@ -123,8 +258,8 @@
.first
.platformDirectory(FlutterPlatform.android);
- final List<String> output =
- await runCapturingPrint(runner, <String>['fetch-deps']);
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--android']);
expect(
processRunner.recordedCalls,
@@ -164,7 +299,8 @@
Error? commandError;
final List<String> output = await runCapturingPrint(
- runner, <String>['fetch-deps'], errorHandler: (Error e) {
+ runner, <String>['fetch-deps', '--no-dart', '--android'],
+ errorHandler: (Error e) {
commandError = e;
});
@@ -199,7 +335,8 @@
Error? commandError;
final List<String> output = await runCapturingPrint(
- runner, <String>['fetch-deps'], errorHandler: (Error e) {
+ runner, <String>['fetch-deps', '--no-dart', '--android'],
+ errorHandler: (Error e) {
commandError = e;
});
@@ -212,41 +349,479 @@
],
));
});
+
+ test('skips non-Android plugins', () async {
+ createFakePlugin('plugin1', packagesDir);
+
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--android']);
+
+ expect(
+ output,
+ containsAllInOrder(
+ <Matcher>[
+ contains('Package does not have native Android dependencies.')
+ ],
+ ));
+ });
+
+ test('skips non-inline plugins', () async {
+ createFakePlugin('plugin1', packagesDir,
+ platformSupport: <String, PlatformDetails>{
+ platformAndroid: const PlatformDetails(PlatformSupport.federated)
+ });
+
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--android']);
+
+ expect(
+ output,
+ containsAllInOrder(
+ <Matcher>[
+ contains('Package does not have native Android dependencies.')
+ ],
+ ));
+ });
});
- test('skips non-Android plugins', () async {
- createFakePlugin('plugin1', packagesDir);
+ group('ios', () {
+ test('runs pub get before pod install', () async {
+ final RepositoryPackage plugin =
+ createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
+ 'example/ios/Flutter/Generated.xcconfig',
+ ], platformSupport: <String, PlatformDetails>{
+ platformIOS: const PlatformDetails(PlatformSupport.inline)
+ });
- final List<String> output =
- await runCapturingPrint(runner, <String>['fetch-deps']);
+ final Directory iOSDir =
+ plugin.getExamples().first.platformDirectory(FlutterPlatform.ios);
- expect(
- output,
- containsAllInOrder(
- <Matcher>[
- contains(
- 'SKIPPING: Plugin does not have an Android implementation.')
+ final List<String> output =
+ await runCapturingPrint(runner, <String>['fetch-deps', '--ios']);
+
+ expect(
+ processRunner.recordedCalls,
+ orderedEquals(<ProcessCall>[
+ const ProcessCall(
+ 'flutter',
+ <String>['precache', '--ios'],
+ null,
+ ),
+ ProcessCall(
+ 'flutter',
+ const <String>['pub', 'get'],
+ plugin.directory.path,
+ ),
+ ProcessCall(
+ 'pod',
+ const <String>['install'],
+ iOSDir.path,
+ ),
+ ]),
+ );
+
+ expect(
+ output,
+ containsAllInOrder(<Matcher>[
+ contains('Running for plugin1'),
+ contains('No issues found!'),
+ ]));
+ });
+
+ test('runs on all examples', () async {
+ final List<String> examples = <String>['example1', 'example2'];
+ final RepositoryPackage plugin = createFakePlugin(
+ 'plugin1', packagesDir,
+ examples: examples,
+ extraFiles: <String>[
+ 'example/example1/ios/Flutter/Generated.xcconfig',
+ 'example/example2/ios/Flutter/Generated.xcconfig',
],
- ));
+ platformSupport: <String, PlatformDetails>{
+ platformIOS: const PlatformDetails(PlatformSupport.inline)
+ });
+
+ final Iterable<Directory> exampleIOSDirs = plugin.getExamples().map(
+ (RepositoryPackage example) =>
+ example.platformDirectory(FlutterPlatform.ios));
+
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--ios']);
+
+ expect(
+ processRunner.recordedCalls,
+ orderedEquals(<ProcessCall>[
+ const ProcessCall(
+ 'flutter',
+ <String>['precache', '--ios'],
+ null,
+ ),
+ for (final Directory directory in exampleIOSDirs)
+ ProcessCall(
+ 'pod',
+ const <String>['install'],
+ directory.path,
+ ),
+ ]),
+ );
+
+ expect(
+ output,
+ containsAllInOrder(<Matcher>[
+ contains('Running for plugin1'),
+ contains('No issues found!'),
+ ]));
+ });
+
+ test('runs pub get if example is not configured', () async {
+ final RepositoryPackage plugin = createFakePlugin(
+ 'plugin1', packagesDir, platformSupport: <String, PlatformDetails>{
+ platformIOS: const PlatformDetails(PlatformSupport.inline)
+ });
+
+ final RepositoryPackage example = plugin.getExamples().first;
+
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--ios']);
+
+ expect(
+ processRunner.recordedCalls,
+ orderedEquals(<ProcessCall>[
+ const ProcessCall(
+ 'flutter',
+ <String>['precache', '--ios'],
+ null,
+ ),
+ ProcessCall(
+ 'flutter',
+ const <String>['pub', 'get'],
+ example.directory.path,
+ ),
+ ProcessCall(
+ 'pod',
+ const <String>['install'],
+ example.platformDirectory(FlutterPlatform.ios).path,
+ ),
+ ]),
+ );
+
+ expect(
+ output,
+ containsAllInOrder(<Matcher>[
+ contains('Running for plugin1'),
+ contains('No issues found!'),
+ ]));
+ });
+
+ test('fails if pre-pod pub get fails', () async {
+ createFakePlugin('plugin1', packagesDir,
+ platformSupport: <String, PlatformDetails>{
+ platformIOS: const PlatformDetails(PlatformSupport.inline)
+ });
+
+ processRunner
+ .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
+ <FakeProcessInfo>[
+ FakeProcessInfo(MockProcess(), <String>['precache']),
+ FakeProcessInfo(MockProcess(exitCode: 1), <String>['pub', 'get']),
+ ];
+
+ Error? commandError;
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--ios'],
+ errorHandler: (Error e) {
+ commandError = e;
+ });
+
+ expect(commandError, isA<ToolExit>());
+ expect(
+ output,
+ containsAllInOrder(
+ <Matcher>[
+ contains('Unable to configure project'),
+ ],
+ ));
+ });
+
+ test('fails if pod install fails', () async {
+ createFakePlugin('plugin1', packagesDir,
+ platformSupport: <String, PlatformDetails>{
+ platformIOS: const PlatformDetails(PlatformSupport.inline)
+ });
+
+ processRunner.mockProcessesForExecutable['pod'] = <FakeProcessInfo>[
+ FakeProcessInfo(MockProcess(exitCode: 1)),
+ ];
+
+ Error? commandError;
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--ios'],
+ errorHandler: (Error e) {
+ commandError = e;
+ });
+
+ expect(commandError, isA<ToolExit>());
+ expect(
+ output,
+ containsAllInOrder(
+ <Matcher>[
+ contains('The following packages had errors:'),
+ ],
+ ));
+ });
+
+ test('skips non-iOS plugins', () async {
+ createFakePlugin('plugin1', packagesDir);
+
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--ios']);
+
+ expect(
+ output,
+ containsAllInOrder(
+ <Matcher>[
+ contains('Package does not have native iOS dependencies.')
+ ],
+ ));
+ });
+
+ test('skips non-inline plugins', () async {
+ createFakePlugin('plugin1', packagesDir,
+ platformSupport: <String, PlatformDetails>{
+ platformIOS: const PlatformDetails(PlatformSupport.federated)
+ });
+
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--ios']);
+
+ expect(
+ output,
+ containsAllInOrder(
+ <Matcher>[
+ contains('Package does not have native iOS dependencies.')
+ ],
+ ));
+ });
});
- test('skips non-inline plugins', () async {
- createFakePlugin('plugin1', packagesDir,
- platformSupport: <String, PlatformDetails>{
- platformAndroid: const PlatformDetails(PlatformSupport.federated)
- });
+ group('macos', () {
+ test('runs pub get before pod install', () async {
+ final RepositoryPackage plugin =
+ createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
+ 'example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig',
+ ], platformSupport: <String, PlatformDetails>{
+ platformMacOS: const PlatformDetails(PlatformSupport.inline)
+ });
- final List<String> output =
- await runCapturingPrint(runner, <String>['fetch-deps']);
+ final Directory macOSDir =
+ plugin.getExamples().first.platformDirectory(FlutterPlatform.macos);
- expect(
- output,
- containsAllInOrder(
- <Matcher>[
- contains(
- 'SKIPPING: Plugin does not have an Android implementation.')
+ final List<String> output =
+ await runCapturingPrint(runner, <String>['fetch-deps', '--macos']);
+
+ expect(
+ processRunner.recordedCalls,
+ orderedEquals(<ProcessCall>[
+ const ProcessCall(
+ 'flutter',
+ <String>['precache', '--macos'],
+ null,
+ ),
+ ProcessCall(
+ 'flutter',
+ const <String>['pub', 'get'],
+ plugin.directory.path,
+ ),
+ ProcessCall(
+ 'pod',
+ const <String>['install'],
+ macOSDir.path,
+ ),
+ ]),
+ );
+
+ expect(
+ output,
+ containsAllInOrder(<Matcher>[
+ contains('Running for plugin1'),
+ contains('No issues found!'),
+ ]));
+ });
+
+ test('runs on all examples', () async {
+ final List<String> examples = <String>['example1', 'example2'];
+ final RepositoryPackage plugin = createFakePlugin(
+ 'plugin1', packagesDir,
+ examples: examples,
+ extraFiles: <String>[
+ 'example/example1/macos/Flutter/ephemeral/Flutter-Generated.xcconfig',
+ 'example/example2/macos/Flutter/ephemeral/Flutter-Generated.xcconfig',
],
- ));
+ platformSupport: <String, PlatformDetails>{
+ platformMacOS: const PlatformDetails(PlatformSupport.inline)
+ });
+
+ final Iterable<Directory> examplemacOSDirs = plugin.getExamples().map(
+ (RepositoryPackage example) =>
+ example.platformDirectory(FlutterPlatform.macos));
+
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--macos']);
+
+ expect(
+ processRunner.recordedCalls,
+ orderedEquals(<ProcessCall>[
+ const ProcessCall(
+ 'flutter',
+ <String>['precache', '--macos'],
+ null,
+ ),
+ for (final Directory directory in examplemacOSDirs)
+ ProcessCall(
+ 'pod',
+ const <String>['install'],
+ directory.path,
+ ),
+ ]),
+ );
+
+ expect(
+ output,
+ containsAllInOrder(<Matcher>[
+ contains('Running for plugin1'),
+ contains('No issues found!'),
+ ]));
+ });
+
+ test('runs pub get if example is not configured', () async {
+ final RepositoryPackage plugin = createFakePlugin(
+ 'plugin1', packagesDir, platformSupport: <String, PlatformDetails>{
+ platformMacOS: const PlatformDetails(PlatformSupport.inline)
+ });
+
+ final RepositoryPackage example = plugin.getExamples().first;
+
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--macos']);
+
+ expect(
+ processRunner.recordedCalls,
+ orderedEquals(<ProcessCall>[
+ const ProcessCall(
+ 'flutter',
+ <String>['precache', '--macos'],
+ null,
+ ),
+ ProcessCall(
+ 'flutter',
+ const <String>['pub', 'get'],
+ example.directory.path,
+ ),
+ ProcessCall(
+ 'pod',
+ const <String>['install'],
+ example.platformDirectory(FlutterPlatform.macos).path,
+ ),
+ ]),
+ );
+
+ expect(
+ output,
+ containsAllInOrder(<Matcher>[
+ contains('Running for plugin1'),
+ contains('No issues found!'),
+ ]));
+ });
+
+ test('fails if pre-pod pub get fails', () async {
+ createFakePlugin('plugin1', packagesDir,
+ platformSupport: <String, PlatformDetails>{
+ platformMacOS: const PlatformDetails(PlatformSupport.inline)
+ });
+
+ processRunner
+ .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
+ <FakeProcessInfo>[
+ FakeProcessInfo(MockProcess(), <String>['precache']),
+ FakeProcessInfo(MockProcess(exitCode: 1), <String>['pub', 'get']),
+ ];
+
+ Error? commandError;
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--macos'],
+ errorHandler: (Error e) {
+ commandError = e;
+ });
+
+ expect(commandError, isA<ToolExit>());
+ expect(
+ output,
+ containsAllInOrder(
+ <Matcher>[
+ contains('Unable to configure project'),
+ ],
+ ));
+ });
+
+ test('fails if pod install fails', () async {
+ createFakePlugin('plugin1', packagesDir,
+ platformSupport: <String, PlatformDetails>{
+ platformMacOS: const PlatformDetails(PlatformSupport.inline)
+ });
+
+ processRunner.mockProcessesForExecutable['pod'] = <FakeProcessInfo>[
+ FakeProcessInfo(MockProcess(exitCode: 1)),
+ ];
+
+ Error? commandError;
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--macos'],
+ errorHandler: (Error e) {
+ commandError = e;
+ });
+
+ expect(commandError, isA<ToolExit>());
+ expect(
+ output,
+ containsAllInOrder(
+ <Matcher>[
+ contains('The following packages had errors:'),
+ ],
+ ));
+ });
+
+ test('skips non-macOS plugins', () async {
+ createFakePlugin('plugin1', packagesDir);
+
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--macos']);
+
+ expect(
+ output,
+ containsAllInOrder(
+ <Matcher>[
+ contains('Package does not have native macOS dependencies.')
+ ],
+ ));
+ });
+
+ test('skips non-inline plugins', () async {
+ createFakePlugin('plugin1', packagesDir,
+ platformSupport: <String, PlatformDetails>{
+ platformMacOS: const PlatformDetails(PlatformSupport.federated)
+ });
+
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['fetch-deps', '--no-dart', '--macos']);
+
+ expect(
+ output,
+ containsAllInOrder(
+ <Matcher>[
+ contains('Package does not have native macOS dependencies.')
+ ],
+ ));
+ });
});
});
}
diff --git a/script/tool/test/util.dart b/script/tool/test/util.dart
index 4f38dd4..1fd6abd 100644
--- a/script/tool/test/util.dart
+++ b/script/tool/test/util.dart
@@ -394,6 +394,7 @@
String executable,
List<String> args, {
Directory? workingDir,
+ Map<String, String>? environment,
bool exitOnError = false,
}) async {
recordedCalls.add(ProcessCall(executable, args, workingDir?.path));
@@ -412,6 +413,7 @@
String executable,
List<String> args, {
Directory? workingDir,
+ Map<String, String>? environment,
bool exitOnError = false,
bool logOnError = false,
Encoding stdoutEncoding = io.systemEncoding,