blob: af1cebe54ed48b0fe17ded1f8dd79dcf34f66c10 [file] [log] [blame]
// Copyright 2014 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 '../base/common.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../plugins.dart';
import '../project.dart';
import 'cocoapods.dart';
import 'swift_package_manager.dart';
/// Flutter has two dependency management solutions for iOS and macOS
/// applications: CocoaPods and Swift Package Manager. They may be used
/// individually or together. This class handles setting up required files and
/// project settings for the dependency manager(s) being used.
class DarwinDependencyManagement {
DarwinDependencyManagement({
required FlutterProject project,
required List<Plugin> plugins,
required CocoaPods cocoapods,
required SwiftPackageManager swiftPackageManager,
required FileSystem fileSystem,
required Logger logger,
}) : _project = project,
_plugins = plugins,
_cocoapods = cocoapods,
_swiftPackageManager = swiftPackageManager,
_fileSystem = fileSystem,
_logger = logger;
final FlutterProject _project;
final List<Plugin> _plugins;
final CocoaPods _cocoapods;
final SwiftPackageManager _swiftPackageManager;
final FileSystem _fileSystem;
final Logger _logger;
/// Generates/updates required files and project settings for Darwin
/// Dependency Managers (CocoaPods and Swift Package Manager). Projects may
/// use only CocoaPods (if no SPM compatible dependencies or SPM has been
/// disabled), only Swift Package Manager (if no CocoaPod dependencies), or
/// both. This only generates files for the manager(s) being used.
///
/// CocoaPods requires a Podfile and certain values in the Flutter xcconfig
/// files.
///
/// Swift Package Manager requires a generated Package.swift and certain
/// settings in the Xcode project's project.pbxproj and xcscheme (done later
/// before build).
Future<void> setUp({
required SupportedPlatform platform,
}) async {
if (platform != SupportedPlatform.ios &&
platform != SupportedPlatform.macos) {
throwToolExit(
'The platform ${platform.name} is incompatible with Darwin Dependency Managers. Only iOS and macOS are allowed.',
);
}
final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios
? _project.ios
: _project.macos;
if (_project.usesSwiftPackageManager) {
await _swiftPackageManager.generatePluginsSwiftPackage(
_plugins,
platform,
xcodeProject,
);
} else if (xcodeProject.flutterPluginSwiftPackageInProjectSettings) {
// If Swift Package Manager is not enabled but the project is already
// integrated for Swift Package Manager, pass no plugins to the generator.
// This will still generate the required Package.swift, but it will have
// no dependencies.
await _swiftPackageManager.generatePluginsSwiftPackage(
<Plugin>[],
platform,
xcodeProject,
);
}
// Skip updating Podfile if project is a module, since it will use a
// different module-specific Podfile.
if (_project.isModule) {
return;
}
final (:int totalCount, :int swiftPackageCount, :int podCount) = await _evaluatePluginsAndPrintWarnings(
platform: platform,
xcodeProject: xcodeProject,
);
final bool useCocoapods;
if (_project.usesSwiftPackageManager) {
useCocoapods = _usingCocoaPodsPlugin(
pluginCount: totalCount,
swiftPackageCount: swiftPackageCount,
cocoapodCount: podCount,
);
} else {
// When Swift Package Manager is not enabled, set up Podfile if plugins
// is not empty, regardless of if plugins are CocoaPod compatible. This
// is done because `processPodsIfNeeded` uses `hasPlugins` to determine
// whether to run.
useCocoapods = _plugins.isNotEmpty;
}
if (useCocoapods) {
await _cocoapods.setupPodfile(xcodeProject);
}
/// The user may have a custom maintained Podfile that they're running `pod install`
/// on themselves.
else if (xcodeProject.podfile.existsSync() && xcodeProject.podfileLock.existsSync()) {
_cocoapods.addPodsDependencyToFlutterXcconfig(xcodeProject);
}
}
bool _usingCocoaPodsPlugin({
required int pluginCount,
required int swiftPackageCount,
required int cocoapodCount,
}) {
if (_project.usesSwiftPackageManager) {
if (pluginCount == swiftPackageCount) {
return false;
}
}
return cocoapodCount > 0;
}
/// Returns count of total number of plugins, number of Swift Package Manager
/// compatible plugins, and number of CocoaPods compatible plugins. A plugin
/// can be both Swift Package Manager and CocoaPods compatible.
///
/// Prints warnings when using a plugin incompatible with the available Darwin
/// Dependency Manager (Swift Package Manager or CocoaPods).
///
/// Prints message prompting the user to deintegrate CocoaPods if using all
/// Swift Package plugins.
Future<({int totalCount, int swiftPackageCount, int podCount})> _evaluatePluginsAndPrintWarnings({
required SupportedPlatform platform,
required XcodeBasedProject xcodeProject,
}) async {
int pluginCount = 0;
int swiftPackageCount = 0;
int cocoapodCount = 0;
for (final Plugin plugin in _plugins) {
if (plugin.platforms[platform.name] == null) {
continue;
}
final String? swiftPackagePath = plugin.pluginSwiftPackageManifestPath(
_fileSystem,
platform.name,
);
final bool swiftPackageManagerCompatible = swiftPackagePath != null &&
_fileSystem.file(swiftPackagePath).existsSync();
final String? podspecPath = plugin.pluginPodspecPath(
_fileSystem,
platform.name,
);
final bool cocoaPodsCompatible =
podspecPath != null && _fileSystem.file(podspecPath).existsSync();
// If a plugin is missing both a Package.swift and Podspec, it won't be
// included by either Swift Package Manager or Cocoapods. This can happen
// when a plugin doesn't have native platform code.
// For example, image_picker_macos only uses dart code.
if (!swiftPackageManagerCompatible && !cocoaPodsCompatible) {
continue;
}
pluginCount += 1;
if (swiftPackageManagerCompatible) {
swiftPackageCount += 1;
}
if (cocoaPodsCompatible) {
cocoapodCount += 1;
}
// If not using Swift Package Manager and plugin does not have podspec
// but does have a Package.swift, throw an error. Otherwise, it'll error
// when it builds.
if (!_project.usesSwiftPackageManager &&
!cocoaPodsCompatible &&
swiftPackageManagerCompatible) {
throwToolExit(
'Plugin ${plugin.name} is only Swift Package Manager compatible. Try '
'enabling Swift Package Manager by running '
'"flutter config --enable-swift-package-manager" or remove the '
'plugin as a dependency.');
}
}
// Only show warnings to remove CocoaPods if the project is using Swift
// Package Manager, has already been migrated to have SPM integration, and
// all plugins are Swift Packages.
if (_project.usesSwiftPackageManager &&
xcodeProject.flutterPluginSwiftPackageInProjectSettings &&
pluginCount == swiftPackageCount &&
swiftPackageCount != 0) {
final bool podfileExists = xcodeProject.podfile.existsSync();
if (podfileExists) {
// If all plugins are Swift Packages and the Podfile matches the
// default template, recommend pod deintegration.
final File podfileTemplate = await _cocoapods.getPodfileTemplate(
xcodeProject,
xcodeProject.xcodeProject,
);
final String configWarning = '${_podIncludeInConfigWarning(xcodeProject, 'Debug')}'
'${_podIncludeInConfigWarning(xcodeProject, 'Release')}';
if (xcodeProject.podfile.readAsStringSync() ==
podfileTemplate.readAsStringSync()) {
_logger.printWarning(
'All plugins found for ${platform.name} are Swift Packages, but your '
'project still has CocoaPods integration. To remove CocoaPods '
'integration, complete the following steps:\n'
' * In the ${platform.name}/ directory run "pod deintegrate"\n'
' * Also in the ${platform.name}/ directory, delete the Podfile\n'
'$configWarning\n'
"Removing CocoaPods integration will improve the project's build time.");
} else {
// If all plugins are Swift Packages, but the Podfile has custom logic,
// recommend migrating manually.
_logger.printWarning(
'All plugins found for ${platform.name} are Swift Packages, but your '
'project still has CocoaPods integration. Your project uses a '
'non-standard Podfile and will need to be migrated to Swift Package '
'Manager manually. Some steps you may need to complete include:\n'
' * In the ${platform.name}/ directory run "pod deintegrate"\n'
' * Transition any Pod dependencies to Swift Package equivalents. '
'See https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app\n'
' * Transition any custom logic\n'
'$configWarning\n'
"Removing CocoaPods integration will improve the project's build time.");
}
}
}
return (
totalCount: pluginCount,
swiftPackageCount: swiftPackageCount,
podCount: cocoapodCount,
);
}
String _podIncludeInConfigWarning(XcodeBasedProject xcodeProject, String mode) {
final File xcconfigFile = xcodeProject.xcodeConfigFor(mode);
final bool configIncludesPods = _cocoapods.xcconfigIncludesPods(xcconfigFile);
if (configIncludesPods) {
return ' * Remove the include to '
'"${_cocoapods.includePodsXcconfig(mode)}" in your '
'${xcconfigFile.parent.parent.basename}/${xcconfigFile.parent.basename}/${xcconfigFile.basename}\n';
}
return '';
}
}