| // Copyright 2017 The Chromium 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 'dart:async'; |
| |
| import 'package:meta/meta.dart'; |
| |
| import '../base/common.dart'; |
| import '../base/context.dart'; |
| import '../base/file_system.dart'; |
| import '../base/io.dart'; |
| import '../base/logger.dart'; |
| import '../base/process.dart'; |
| import '../base/process_manager.dart'; |
| import '../base/version.dart'; |
| import '../cache.dart'; |
| import '../globals.dart'; |
| import 'xcodeproj.dart'; |
| |
| const String noCocoaPodsConsequence = ''' |
| CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side. |
| Without resolving iOS dependencies with CocoaPods, plugins will not work on iOS. |
| For more info, see https://flutter.io/platform-plugins'''; |
| |
| const String cocoaPodsInstallInstructions = ''' |
| brew install cocoapods |
| pod setup'''; |
| |
| const String cocoaPodsUpgradeInstructions = ''' |
| brew upgrade cocoapods |
| pod setup'''; |
| |
| CocoaPods get cocoaPods => context.putIfAbsent(CocoaPods, () => const CocoaPods()); |
| |
| class CocoaPods { |
| const CocoaPods(); |
| |
| Future<bool> get hasCocoaPods => exitsHappyAsync(<String>['pod', '--version']); |
| |
| String get cocoaPodsMinimumVersion => '1.0.0'; |
| |
| Future<String> get cocoaPodsVersionText async => (await runAsync(<String>['pod', '--version'])).processResult.stdout.trim(); |
| |
| Future<bool> get isCocoaPodsInstalledAndMeetsVersionCheck async { |
| if (!await hasCocoaPods) |
| return false; |
| try { |
| final Version installedVersion = new Version.parse(await cocoaPodsVersionText); |
| return installedVersion >= new Version.parse(cocoaPodsMinimumVersion); |
| } on FormatException { |
| return false; |
| } |
| } |
| |
| /// Whether CocoaPods ran 'pod setup' once where the costly pods' specs are cloned. |
| Future<bool> get isCocoaPodsInitialized => fs.isDirectory(fs.path.join(homeDirPath, '.cocoapods', 'repos', 'master')); |
| |
| Future<Null> processPods({ |
| @required Directory appIosDirectory, |
| // For backward compatibility with previously created Podfile only. |
| @required String iosEngineDir, |
| bool isSwift: false, |
| bool flutterPodChanged: true, |
| }) async { |
| if (!(await appIosDirectory.childFile('Podfile').exists())) { |
| throwToolExit('Podfile missing'); |
| } |
| if (await _checkPodCondition()) { |
| if (_shouldRunPodInstall(appIosDirectory, flutterPodChanged)) { |
| await _runPodInstall(appIosDirectory, iosEngineDir); |
| } |
| } |
| } |
| |
| /// Make sure the CocoaPods tools are in the right states. |
| Future<bool> _checkPodCondition() async { |
| if (!await isCocoaPodsInstalledAndMeetsVersionCheck) { |
| final String minimumVersion = cocoaPodsMinimumVersion; |
| printError( |
| 'Warning: CocoaPods version $minimumVersion or greater not installed. Skipping pod install.\n' |
| '$noCocoaPodsConsequence\n' |
| 'To install:\n' |
| '$cocoaPodsInstallInstructions\n', |
| emphasis: true, |
| ); |
| return false; |
| } |
| if (!await isCocoaPodsInitialized) { |
| printError( |
| 'Warning: CocoaPods installed but not initialized. Skipping pod install.\n' |
| '$noCocoaPodsConsequence\n' |
| 'To initialize CocoaPods, run:\n' |
| ' pod setup\n' |
| 'once to finalize CocoaPods\' installation.', |
| emphasis: true, |
| ); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /// Ensures the `ios` sub-project of the Flutter project at [appDirectory] |
| /// contains a suitable `Podfile` and that its `Flutter/Xxx.xcconfig` files |
| /// include pods configuration. |
| void setupPodfile(String appDirectory) { |
| if (!xcodeProjectInterpreter.isInstalled) { |
| // Don't do anything for iOS when host platform doesn't support it. |
| return; |
| } |
| final String podfilePath = fs.path.join(appDirectory, 'ios', 'Podfile'); |
| if (!fs.file(podfilePath).existsSync()) { |
| final bool isSwift = xcodeProjectInterpreter.getBuildSettings( |
| fs.path.join(appDirectory, 'ios', 'Runner.xcodeproj'), |
| 'Runner', |
| ).containsKey('SWIFT_VERSION'); |
| final File podfileTemplate = fs.file(fs.path.join( |
| Cache.flutterRoot, |
| 'packages', |
| 'flutter_tools', |
| 'templates', |
| 'cocoapods', |
| isSwift ? 'Podfile-swift' : 'Podfile-objc', |
| )); |
| podfileTemplate.copySync(podfilePath); |
| } |
| _addPodsDependencyToFlutterXcconfig(appDirectory, 'Debug'); |
| _addPodsDependencyToFlutterXcconfig(appDirectory, 'Release'); |
| } |
| |
| void _addPodsDependencyToFlutterXcconfig(String appDirectory, String mode) { |
| final File file = fs.file(fs.path.join(appDirectory, 'ios', 'Flutter', '$mode.xcconfig')); |
| if (file.existsSync()) { |
| final String content = file.readAsStringSync(); |
| final String include = '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode |
| .toLowerCase()}.xcconfig"'; |
| if (!content.contains(include)) |
| file.writeAsStringSync('$include\n$content', flush: true); |
| } |
| } |
| |
| /// Ensures that pod install is deemed needed on next check. |
| void invalidatePodInstallOutput(String appDirectory) { |
| final File manifest = fs.file( |
| fs.path.join(appDirectory, 'ios', 'Pods', 'Manifest.lock'), |
| ); |
| if (manifest.existsSync()) |
| manifest.deleteSync(); |
| } |
| |
| // Check if you need to run pod install. |
| // The pod install will run if any of below is true. |
| // 1. The flutter.framework has changed (debug/release/profile) |
| // 2. The podfile.lock doesn't exist |
| // 3. The Pods/Manifest.lock doesn't exist (It is deleted when plugins change) |
| // 4. The podfile.lock doesn't match Pods/Manifest.lock. |
| bool _shouldRunPodInstall(Directory appIosDirectory, bool flutterPodChanged) { |
| if (flutterPodChanged) |
| return true; |
| // Check if podfile.lock and Pods/Manifest.lock exist and match. |
| final File podfileLockFile = appIosDirectory.childFile('Podfile.lock'); |
| final File manifestLockFile = |
| appIosDirectory.childFile(fs.path.join('Pods', 'Manifest.lock')); |
| return !podfileLockFile.existsSync() |
| || !manifestLockFile.existsSync() |
| || podfileLockFile.readAsStringSync() != manifestLockFile.readAsStringSync(); |
| } |
| |
| Future<Null> _runPodInstall(Directory appIosDirectory, String engineDirectory) async { |
| final Status status = logger.startProgress('Running pod install...', expectSlowOperation: true); |
| final ProcessResult result = await processManager.run( |
| <String>['pod', 'install', '--verbose'], |
| workingDirectory: appIosDirectory.path, |
| environment: <String, String>{ |
| // For backward compatibility with previously created Podfile only. |
| 'FLUTTER_FRAMEWORK_DIR': engineDirectory, |
| // See https://github.com/flutter/flutter/issues/10873. |
| // CocoaPods analytics adds a lot of latency. |
| 'COCOAPODS_DISABLE_STATS': 'true', |
| }, |
| ); |
| status.stop(); |
| if (logger.isVerbose || result.exitCode != 0) { |
| if (result.stdout.isNotEmpty) { |
| printStatus('CocoaPods\' output:\n↳'); |
| printStatus(result.stdout, indent: 4); |
| } |
| if (result.stderr.isNotEmpty) { |
| printStatus('Error output from CocoaPods:\n↳'); |
| printStatus(result.stderr, indent: 4); |
| } |
| } |
| if (result.exitCode != 0) { |
| invalidatePodInstallOutput(appIosDirectory.parent.path); |
| _diagnosePodInstallFailure(result); |
| throwToolExit('Error running pod install'); |
| } |
| } |
| |
| void _diagnosePodInstallFailure(ProcessResult result) { |
| if (result.stdout is String && result.stdout.contains('out-of-date source repos')) { |
| printError( |
| "Error: CocoaPods's specs repository is too out-of-date to satisfy dependencies.\n" |
| 'To update the CocoaPods specs, run:\n' |
| ' pod repo update\n', |
| emphasis: true, |
| ); |
| } |
| } |
| } |