blob: 2f763a04c263b9d8e12805c0be552c6338817198 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/file.dart';
import 'common/core.dart';
import 'common/gradle.dart';
import 'common/output_utils.dart';
import 'common/package_looping_command.dart';
import 'common/plugin_utils.dart';
import 'common/repository_package.dart';
const int _exitPrecacheFailed = 3;
const int _exitNothingRequested = 4;
/// Download dependencies, both Dart and native.
///
/// Specficially each platform runs:
/// Android: 'gradlew dependencies'.
/// 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 {
/// Creates an instance of the fetch-deps command.
FetchDepsCommand(
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 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(
'Package does not have native Android dependencies.');
}
for (final RepositoryPackage example in package.getExamples()) {
final GradleProject gradleProject = GradleProject(example,
processRunner: processRunner, platform: platform);
if (!gradleProject.isConfigured()) {
final int exitCode = await processRunner.runAndStream(
flutterCommand,
<String>['build', 'apk', '--config-only'],
workingDir: example.directory,
);
if (exitCode != 0) {
printError('Unable to configure Gradle project.');
return PackageResult.fail(<String>['Unable to configure Gradle.']);
}
}
final String packageName = package.directory.basename;
final int exitCode =
await gradleProject.runCommand('$packageName:dependencies');
if (exitCode != 0) {
return PackageResult.fail();
}
}
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));
}