[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
This commit is contained in:
stuartmorgan
2023-07-26 06:06:22 -07:00
committed by GitHub
parent 406eac1ad9
commit a99fc8765d
15 changed files with 901 additions and 63 deletions

View File

@ -4,6 +4,10 @@ tasks:
infra_step: true # Note infra steps failing prevents "always" from running. infra_step: true # Note infra steps failing prevents "always" from running.
- name: analyze repo tools - name: analyze repo tools
script: .ci/scripts/analyze_repo_tools.sh script: .ci/scripts/analyze_repo_tools.sh
- name: download Dart deps
script: script/tool_runner.sh
args: ["fetch-deps"]
infra_step: true
- name: analyze - name: analyze
script: script/tool_runner.sh script: script/tool_runner.sh
# DO NOT change the custom-analysis argument here without changing the Dart repo. # DO NOT change the custom-analysis argument here without changing the Dart repo.

View File

@ -2,10 +2,10 @@ tasks:
- name: prepare tool - name: prepare tool
script: .ci/scripts/prepare_tool.sh script: .ci/scripts/prepare_tool.sh
infra_step: true # Note infra steps failing prevents "always" from running. 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 script: script/tool_runner.sh
infra_step: true infra_step: true
args: ["fetch-deps"] args: ["fetch-deps", "--android", "--supporting-target-platforms-only"]
- name: build examples - name: build examples
script: script/tool_runner.sh script: script/tool_runner.sh
args: ["build-examples", "--apk"] args: ["build-examples", "--apk"]

View File

@ -5,6 +5,10 @@ tasks:
- name: create simulator - name: create simulator
script: .ci/scripts/create_simulator.sh script: .ci/scripts/create_simulator.sh
infra_step: true # Note infra steps failing prevents "always" from running. 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 - name: build examples
script: script/tool_runner.sh script: script/tool_runner.sh
args: ["build-examples", "--ios"] args: ["build-examples", "--ios"]

View File

@ -3,6 +3,11 @@ tasks:
script: .ci/scripts/prepare_tool.sh script: .ci/scripts/prepare_tool.sh
- name: set default apps - name: set default apps
script: .ci/scripts/set_default_linux_apps.sh 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 - name: build examples
script: script/tool_runner.sh script: script/tool_runner.sh
args: ["build-examples", "--linux"] args: ["build-examples", "--linux"]

View File

@ -2,6 +2,10 @@ tasks:
- name: prepare tool - name: prepare tool
script: .ci/scripts/prepare_tool.sh script: .ci/scripts/prepare_tool.sh
infra_step: true # Note infra steps failing prevents "always" from running. 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 - name: build examples
script: script/tool_runner.sh script: script/tool_runner.sh
args: ["build-examples", "--macos"] args: ["build-examples", "--macos"]

View File

@ -2,6 +2,10 @@ tasks:
- name: prepare tool - name: prepare tool
script: .ci/scripts/prepare_tool.sh script: .ci/scripts/prepare_tool.sh
infra_step: true # Note infra steps failing prevents "always" from running. 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 - name: build examples
script: script/tool_runner.sh script: script/tool_runner.sh
args: ["build-examples", "--web"] args: ["build-examples", "--web"]

View File

@ -2,6 +2,10 @@ tasks:
- name: prepare tool - name: prepare tool
script: .ci/scripts/prepare_tool.sh script: .ci/scripts/prepare_tool.sh
infra_step: true # Note infra steps failing prevents "always" from running. 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) - name: build examples (Win32)
script: .ci/scripts/build_examples_win32.sh script: .ci/scripts/build_examples_win32.sh
- name: native unit tests (Win32) - name: native unit tests (Win32)

View File

@ -176,9 +176,8 @@ class BuildExamplesCommand extends PackageLoopingCommand {
// supported platforms. For packages, just log and skip any requested // supported platforms. For packages, just log and skip any requested
// platform that a package doesn't have set up. // platform that a package doesn't have set up.
if (!isPlugin && if (!isPlugin &&
!example.directory !example.appSupportsPlatform(
.childDirectory(platform.flutterPlatformDirectory) getPlatformByName(platform.pluginPlatform))) {
.existsSync()) {
print('Skipping ${platform.label} for $packageName; not supported.'); print('Skipping ${platform.label} for $packageName; not supported.');
continue; continue;
} }
@ -304,11 +303,6 @@ class _PlatformDetails {
/// The `flutter build` build type. /// The `flutter build` build type.
final String flutterBuildType; 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`. /// Any extra flags to pass to `flutter build`.
final List<String> extraBuildFlags; final List<String> extraBuildFlags;
} }

View File

@ -37,6 +37,25 @@ const String kEnableExperiment = 'enable-experiment';
// ignore: public_member_api_docs // ignore: public_member_api_docs
enum FlutterPlatform { android, ios, linux, macos, web, windows } 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 // Flutter->Dart SDK version mapping. Any time a command fails to look up a
// corresponding version, this map should be updated. // corresponding version, this map should be updated.
final Map<Version, Version> _dartSdkForFlutterSdk = <Version, Version>{ final Map<Version, Version> _dartSdkForFlutterSdk = <Version, Version>{

View File

@ -30,12 +30,13 @@ class ProcessRunner {
String executable, String executable,
List<String> args, { List<String> args, {
Directory? workingDir, Directory? workingDir,
Map<String, String>? environment,
bool exitOnError = false, bool exitOnError = false,
}) async { }) async {
print( print(
'Running command: "$executable ${args.join(' ')}" in ${workingDir?.path ?? io.Directory.current.path}'); 'Running command: "$executable ${args.join(' ')}" in ${workingDir?.path ?? io.Directory.current.path}');
final io.Process process = await io.Process.start(executable, args, final io.Process process = await io.Process.start(executable, args,
workingDirectory: workingDir?.path); workingDirectory: workingDir?.path, environment: environment);
await Future.wait(<Future<dynamic>>[ await Future.wait(<Future<dynamic>>[
io.stdout.addStream(process.stdout), io.stdout.addStream(process.stdout),
io.stderr.addStream(process.stderr), io.stderr.addStream(process.stderr),
@ -62,14 +63,19 @@ class ProcessRunner {
/// Defaults to `false` /// Defaults to `false`
/// ///
/// Returns the [io.ProcessResult] of the [executable]. /// Returns the [io.ProcessResult] of the [executable].
Future<io.ProcessResult> run(String executable, List<String> args, Future<io.ProcessResult> run(
{Directory? workingDir, String executable,
bool exitOnError = false, List<String> args, {
bool logOnError = false, Directory? workingDir,
Encoding stdoutEncoding = io.systemEncoding, Map<String, String>? environment,
Encoding stderrEncoding = io.systemEncoding}) async { 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, final io.ProcessResult result = await io.Process.run(executable, args,
workingDirectory: workingDir?.path, workingDirectory: workingDir?.path,
environment: environment,
stdoutEncoding: stdoutEncoding, stdoutEncoding: stdoutEncoding,
stderrEncoding: stderrEncoding); stderrEncoding: stderrEncoding);
if (result.exitCode != 0) { if (result.exitCode != 0) {

View File

@ -92,6 +92,16 @@ class RepositoryPackage {
return directory.childDirectory(directoryName); 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 = late final Pubspec _parsedPubspec =
Pubspec.parse(pubspecFile.readAsStringSync()); Pubspec.parse(pubspecFile.readAsStringSync());

View File

@ -254,7 +254,7 @@ class DriveExamplesCommand extends PackageLoopingCommand {
for (final MapEntry<String, List<String>> entry for (final MapEntry<String, List<String>> entry
in _targetDeviceFlags.entries) { in _targetDeviceFlags.entries) {
final String platform = entry.key; final String platform = entry.key;
if (example.directory.childDirectory(platform).existsSync()) { if (example.appSupportsPlatform(getPlatformByName(platform))) {
deviceFlags.addAll(entry.value); deviceFlags.addAll(entry.value);
} else { } else {
final String exampleName = final String exampleName =

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:file/file.dart';
import 'common/core.dart'; import 'common/core.dart';
import 'common/gradle.dart'; import 'common/gradle.dart';
@ -10,12 +11,15 @@ import 'common/package_looping_command.dart';
import 'common/plugin_utils.dart'; import 'common/plugin_utils.dart';
import 'common/repository_package.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: /// Specficially each platform runs:
/// Android: 'gradlew dependencies'. /// Android: 'gradlew dependencies'.
/// Dart: TBD (flutter/flutter/issues/130279) /// Dart: 'flutter pub get'.
/// iOS: TBD (flutter/flutter/issues/130280) /// iOS/macOS: 'pod install'.
/// ///
/// See https://docs.gradle.org/6.4/userguide/core_dependency_management.html#sec:dependency-mgmt-in-gradle. /// See https://docs.gradle.org/6.4/userguide/core_dependency_management.html#sec:dependency-mgmt-in-gradle.
class FetchDepsCommand extends PackageLoopingCommand { class FetchDepsCommand extends PackageLoopingCommand {
@ -24,25 +28,153 @@ class FetchDepsCommand extends PackageLoopingCommand {
super.packagesDir, { super.packagesDir, {
super.processRunner, super.processRunner,
super.platform, 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 @override
final String name = 'fetch-deps'; final String name = 'fetch-deps';
@override @override
final String description = 'Fetches dependencies for plugins.\n' final String description = 'Fetches dependencies for packages';
'Runs "gradlew dependencies" on Android plugins.\n'
'Dart see flutter/flutter/issues/130279\n' @override
'iOS plugins see flutter/flutter/issues/130280\n' Future<void> initializeRun() async {
'\n' // `pod install` requires having the platform artifacts precached. See
'Requires the examples to have been built at least once before running.'; // 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 @override
Future<PackageResult> runForPackage(RepositoryPackage package) async { 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, if (!pluginSupportsPlatform(platformAndroid, package,
requiredMode: PlatformSupport.inline)) { requiredMode: PlatformSupport.inline)) {
return PackageResult.skip( return PackageResult.skip(
'Plugin does not have an Android implementation.'); 'Package does not have native Android dependencies.');
} }
for (final RepositoryPackage example in package.getExamples()) { for (final RepositoryPackage example in package.getExamples()) {
@ -63,7 +195,8 @@ class FetchDepsCommand extends PackageLoopingCommand {
final String packageName = package.directory.basename; 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) { if (exitCode != 0) {
return PackageResult.fail(); return PackageResult.fail();
} }
@ -71,4 +204,78 @@ class FetchDepsCommand extends PackageLoopingCommand {
return PackageResult.success(); 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));
} }

View File

@ -36,7 +36,142 @@ void main() {
CommandRunner<void>('fetch_deps_test', 'Test for $FetchDepsCommand'); CommandRunner<void>('fetch_deps_test', 'Test for $FetchDepsCommand');
runner.addCommand(command); 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', () { 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 { test('runs gradlew dependencies', () async {
final RepositoryPackage plugin = final RepositoryPackage plugin =
createFakePlugin('plugin1', packagesDir, extraFiles: <String>[ createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
@ -50,8 +185,8 @@ void main() {
.first .first
.platformDirectory(FlutterPlatform.android); .platformDirectory(FlutterPlatform.android);
final List<String> output = final List<String> output = await runCapturingPrint(
await runCapturingPrint(runner, <String>['fetch-deps']); runner, <String>['fetch-deps', '--no-dart', '--android']);
expect( expect(
processRunner.recordedCalls, processRunner.recordedCalls,
@ -89,8 +224,8 @@ void main() {
(RepositoryPackage example) => (RepositoryPackage example) =>
example.platformDirectory(FlutterPlatform.android)); example.platformDirectory(FlutterPlatform.android));
final List<String> output = final List<String> output = await runCapturingPrint(
await runCapturingPrint(runner, <String>['fetch-deps']); runner, <String>['fetch-deps', '--no-dart', '--android']);
expect( expect(
processRunner.recordedCalls, processRunner.recordedCalls,
@ -123,8 +258,8 @@ void main() {
.first .first
.platformDirectory(FlutterPlatform.android); .platformDirectory(FlutterPlatform.android);
final List<String> output = final List<String> output = await runCapturingPrint(
await runCapturingPrint(runner, <String>['fetch-deps']); runner, <String>['fetch-deps', '--no-dart', '--android']);
expect( expect(
processRunner.recordedCalls, processRunner.recordedCalls,
@ -164,7 +299,8 @@ void main() {
Error? commandError; Error? commandError;
final List<String> output = await runCapturingPrint( 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; commandError = e;
}); });
@ -199,7 +335,8 @@ void main() {
Error? commandError; Error? commandError;
final List<String> output = await runCapturingPrint( 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; commandError = e;
}); });
@ -212,41 +349,479 @@ void main() {
], ],
)); ));
}); });
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 { group('ios', () {
createFakePlugin('plugin1', packagesDir); 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 = final Directory iOSDir =
await runCapturingPrint(runner, <String>['fetch-deps']); plugin.getExamples().first.platformDirectory(FlutterPlatform.ios);
expect( final List<String> output =
output, await runCapturingPrint(runner, <String>['fetch-deps', '--ios']);
containsAllInOrder(
<Matcher>[ expect(
contains( processRunner.recordedCalls,
'SKIPPING: Plugin does not have an Android implementation.') 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 { group('macos', () {
createFakePlugin('plugin1', packagesDir, test('runs pub get before pod install', () async {
platformSupport: <String, PlatformDetails>{ final RepositoryPackage plugin =
platformAndroid: const PlatformDetails(PlatformSupport.federated) createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
}); 'example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig',
], platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline)
});
final List<String> output = final Directory macOSDir =
await runCapturingPrint(runner, <String>['fetch-deps']); plugin.getExamples().first.platformDirectory(FlutterPlatform.macos);
expect( final List<String> output =
output, await runCapturingPrint(runner, <String>['fetch-deps', '--macos']);
containsAllInOrder(
<Matcher>[ expect(
contains( processRunner.recordedCalls,
'SKIPPING: Plugin does not have an Android implementation.') 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.')
],
));
});
}); });
}); });
} }

View File

@ -394,6 +394,7 @@ class RecordingProcessRunner extends ProcessRunner {
String executable, String executable,
List<String> args, { List<String> args, {
Directory? workingDir, Directory? workingDir,
Map<String, String>? environment,
bool exitOnError = false, bool exitOnError = false,
}) async { }) async {
recordedCalls.add(ProcessCall(executable, args, workingDir?.path)); recordedCalls.add(ProcessCall(executable, args, workingDir?.path));
@ -412,6 +413,7 @@ class RecordingProcessRunner extends ProcessRunner {
String executable, String executable,
List<String> args, { List<String> args, {
Directory? workingDir, Directory? workingDir,
Map<String, String>? environment,
bool exitOnError = false, bool exitOnError = false,
bool logOnError = false, bool logOnError = false,
Encoding stdoutEncoding = io.systemEncoding, Encoding stdoutEncoding = io.systemEncoding,