"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter_tools/lib/src/macos/cocoapods.dart" (13 Nov 2020, 16418 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // Copyright 2014 The Flutter Authors. All rights reserved.
    2 // Use of this source code is governed by a BSD-style license that can be
    3 // found in the LICENSE file.
    4 
    5 import 'dart:async';
    6 
    7 import 'package:file/file.dart';
    8 import 'package:meta/meta.dart';
    9 import 'package:process/process.dart';
   10 
   11 import '../base/common.dart';
   12 import '../base/file_system.dart';
   13 import '../base/io.dart';
   14 import '../base/logger.dart';
   15 import '../base/platform.dart';
   16 import '../base/process.dart';
   17 import '../base/version.dart';
   18 import '../cache.dart';
   19 import '../ios/xcodeproj.dart';
   20 import '../project.dart';
   21 
   22 const String noCocoaPodsConsequence = '''
   23   CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart side.
   24   Without CocoaPods, plugins will not work on iOS or macOS.
   25   For more info, see https://flutter.dev/platform-plugins''';
   26 
   27 const String unknownCocoaPodsConsequence = '''
   28   Flutter is unable to determine the installed CocoaPods's version.
   29   Ensure that the output of 'pod --version' contains only digits and . to be recognized by Flutter.''';
   30 
   31 const String brokenCocoaPodsConsequence = '''
   32   You appear to have CocoaPods installed but it is not working.
   33   This can happen if the version of Ruby that CocoaPods was installed with is different from the one being used to invoke it.
   34   This can usually be fixed by re-installing CocoaPods. For more info, see https://github.com/flutter/flutter/issues/14293.''';
   35 
   36 const String outOfDateFrameworksPodfileConsequence = '''
   37   This can cause a mismatched version of Flutter to be embedded in your app, which may result in App Store submission rejection or crashes.
   38   If you have local Podfile edits you would like to keep, see https://github.com/flutter/flutter/issues/24641 for instructions.''';
   39 
   40 const String outOfDatePluginsPodfileConsequence = '''
   41   This can cause issues if your application depends on plugins that do not support iOS.
   42   See https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms for details.
   43   If you have local Podfile edits you would like to keep, see https://github.com/flutter/flutter/issues/45197 for instructions.''';
   44 
   45 const String cocoaPodsInstallInstructions = '''
   46   sudo gem install cocoapods''';
   47 
   48 const String cocoaPodsUpgradeInstructions = '''
   49   sudo gem install cocoapods''';
   50 
   51 const String podfileMigrationInstructions = '''
   52   rm ios/Podfile''';
   53 
   54 /// Result of evaluating the CocoaPods installation.
   55 enum CocoaPodsStatus {
   56   /// iOS plugins will not work, installation required.
   57   notInstalled,
   58   /// iOS plugins might not work, upgrade recommended.
   59   unknownVersion,
   60   /// iOS plugins will not work, upgrade required.
   61   belowMinimumVersion,
   62   /// iOS plugins may not work in certain situations (Swift, static libraries),
   63   /// upgrade recommended.
   64   belowRecommendedVersion,
   65   /// Everything should be fine.
   66   recommended,
   67   /// iOS plugins will not work, re-install required.
   68   brokenInstall,
   69 }
   70 
   71 class CocoaPods {
   72   CocoaPods({
   73     @required FileSystem fileSystem,
   74     @required ProcessManager processManager,
   75     @required XcodeProjectInterpreter xcodeProjectInterpreter,
   76     @required Logger logger,
   77     @required Platform platform,
   78     @required TimeoutConfiguration timeoutConfiguration,
   79   }) : _fileSystem = fileSystem,
   80       _processManager = processManager,
   81       _xcodeProjectInterpreter = xcodeProjectInterpreter,
   82       _logger = logger,
   83       _platform = platform,
   84       _processUtils = ProcessUtils(processManager: processManager, logger: logger),
   85       _fileSystemUtils = FileSystemUtils(fileSystem: fileSystem, platform: platform),
   86       _timeoutConfiguration = timeoutConfiguration;
   87 
   88   final FileSystem _fileSystem;
   89   final ProcessManager _processManager;
   90   final FileSystemUtils _fileSystemUtils;
   91   final ProcessUtils _processUtils;
   92   final XcodeProjectInterpreter _xcodeProjectInterpreter;
   93   final Logger _logger;
   94   final Platform _platform;
   95   final TimeoutConfiguration _timeoutConfiguration;
   96 
   97   Future<String> _versionText;
   98 
   99   String get cocoaPodsMinimumVersion => '1.6.0';
  100   String get cocoaPodsRecommendedVersion => '1.8.0';
  101 
  102   Future<bool> get isInstalled =>
  103     _processUtils.exitsHappy(<String>['which', 'pod']);
  104 
  105   Future<String> get cocoaPodsVersionText {
  106     _versionText ??= _processUtils.run(
  107       <String>['pod', '--version'],
  108       environment: <String, String>{
  109         'LANG': 'en_US.UTF-8',
  110       },
  111     ).then<String>((RunResult result) {
  112       return result.exitCode == 0 ? result.stdout.trim() : null;
  113     }, onError: (dynamic _) => null);
  114     return _versionText;
  115   }
  116 
  117   Future<CocoaPodsStatus> get evaluateCocoaPodsInstallation async {
  118     if (!(await isInstalled)) {
  119       return CocoaPodsStatus.notInstalled;
  120     }
  121     final String versionText = await cocoaPodsVersionText;
  122     if (versionText == null) {
  123       return CocoaPodsStatus.brokenInstall;
  124     }
  125     try {
  126       final Version installedVersion = Version.parse(versionText);
  127       if (installedVersion == null) {
  128         return CocoaPodsStatus.unknownVersion;
  129       }
  130       if (installedVersion < Version.parse(cocoaPodsMinimumVersion)) {
  131         return CocoaPodsStatus.belowMinimumVersion;
  132       }
  133       if (installedVersion < Version.parse(cocoaPodsRecommendedVersion)) {
  134         return CocoaPodsStatus.belowRecommendedVersion;
  135       }
  136       return CocoaPodsStatus.recommended;
  137     } on FormatException {
  138       return CocoaPodsStatus.notInstalled;
  139     }
  140   }
  141 
  142   /// Whether CocoaPods ran 'pod setup' once where the costly pods' specs are
  143   /// cloned.
  144   ///
  145   /// Versions >= 1.8.0 do not require 'pod setup' and default to a CDN instead
  146   /// of a locally cloned repository.
  147   /// See http://blog.cocoapods.org/CocoaPods-1.8.0-beta/
  148   ///
  149   /// A user can override the default location via the CP_REPOS_DIR environment
  150   /// variable.
  151   ///
  152   /// See https://github.com/CocoaPods/CocoaPods/blob/master/lib/cocoapods/config.rb#L138
  153   /// for details of this variable.
  154   Future<bool> get isCocoaPodsInitialized async {
  155     final Version installedVersion = Version.parse(await cocoaPodsVersionText);
  156     if (installedVersion != null && installedVersion >= Version.parse('1.8.0')) {
  157       return true;
  158     }
  159     final String cocoapodsReposDir = _platform.environment['CP_REPOS_DIR']
  160       ?? _fileSystem.path.join(_fileSystemUtils.homeDirPath, '.cocoapods', 'repos');
  161     return _fileSystem.isDirectory(_fileSystem.path.join(cocoapodsReposDir, 'master'));
  162   }
  163 
  164   Future<bool> processPods({
  165     @required XcodeBasedProject xcodeProject,
  166     // For backward compatibility with previously created Podfile only.
  167     @required String engineDir,
  168     bool dependenciesChanged = true,
  169   }) async {
  170     if (!xcodeProject.podfile.existsSync()) {
  171       throwToolExit('Podfile missing');
  172     }
  173     bool podsProcessed = false;
  174     if (_shouldRunPodInstall(xcodeProject, dependenciesChanged)) {
  175       if (!await _checkPodCondition()) {
  176         throwToolExit('CocoaPods not installed or not in valid state.');
  177       }
  178       await _runPodInstall(xcodeProject, engineDir);
  179       podsProcessed = true;
  180     }
  181     _warnIfPodfileOutOfDate(xcodeProject);
  182     return podsProcessed;
  183   }
  184 
  185   /// Make sure the CocoaPods tools are in the right states.
  186   Future<bool> _checkPodCondition() async {
  187     final CocoaPodsStatus installation = await evaluateCocoaPodsInstallation;
  188     switch (installation) {
  189       case CocoaPodsStatus.notInstalled:
  190         _logger.printError(
  191           'Warning: CocoaPods not installed. Skipping pod install.\n'
  192           '$noCocoaPodsConsequence\n'
  193           'To install:\n'
  194           '$cocoaPodsInstallInstructions\n',
  195           emphasis: true,
  196         );
  197         return false;
  198       case CocoaPodsStatus.brokenInstall:
  199         _logger.printError(
  200           'Warning: CocoaPods is installed but broken. Skipping pod install.\n'
  201           '$brokenCocoaPodsConsequence\n'
  202           'To re-install:\n'
  203           '$cocoaPodsUpgradeInstructions\n',
  204           emphasis: true,
  205         );
  206         return false;
  207       case CocoaPodsStatus.unknownVersion:
  208         _logger.printError(
  209           'Warning: Unknown CocoaPods version installed.\n'
  210           '$unknownCocoaPodsConsequence\n'
  211           'To upgrade:\n'
  212           '$cocoaPodsUpgradeInstructions\n',
  213           emphasis: true,
  214         );
  215         break;
  216       case CocoaPodsStatus.belowMinimumVersion:
  217         _logger.printError(
  218           'Warning: CocoaPods minimum required version $cocoaPodsMinimumVersion or greater not installed. Skipping pod install.\n'
  219           '$noCocoaPodsConsequence\n'
  220           'To upgrade:\n'
  221           '$cocoaPodsUpgradeInstructions\n',
  222           emphasis: true,
  223         );
  224         return false;
  225       case CocoaPodsStatus.belowRecommendedVersion:
  226         _logger.printError(
  227           'Warning: CocoaPods recommended version $cocoaPodsRecommendedVersion or greater not installed.\n'
  228           'Pods handling may fail on some projects involving plugins.\n'
  229           'To upgrade:\n'
  230           '$cocoaPodsUpgradeInstructions\n',
  231           emphasis: true,
  232         );
  233         break;
  234       case CocoaPodsStatus.recommended:
  235         break;
  236     }
  237     if (!await isCocoaPodsInitialized) {
  238       _logger.printError(
  239         'Warning: CocoaPods installed but not initialized. Skipping pod install.\n'
  240         '$noCocoaPodsConsequence\n'
  241         'To initialize CocoaPods, run:\n'
  242         '  pod setup\n'
  243         "once to finalize CocoaPods' installation.",
  244         emphasis: true,
  245       );
  246       return false;
  247     }
  248 
  249     return true;
  250   }
  251 
  252   /// Ensures the given Xcode-based sub-project of a parent Flutter project
  253   /// contains a suitable `Podfile` and that its `Flutter/Xxx.xcconfig` files
  254   /// include pods configuration.
  255   Future<void> setupPodfile(XcodeBasedProject xcodeProject) async {
  256     if (!_xcodeProjectInterpreter.isInstalled) {
  257       // Don't do anything for iOS when host platform doesn't support it.
  258       return;
  259     }
  260     final Directory runnerProject = xcodeProject.xcodeProject;
  261     if (!runnerProject.existsSync()) {
  262       return;
  263     }
  264     final File podfile = xcodeProject.podfile;
  265     if (podfile.existsSync()) {
  266       addPodsDependencyToFlutterXcconfig(xcodeProject);
  267       return;
  268     }
  269     String podfileTemplateName;
  270     if (xcodeProject is MacOSProject) {
  271       podfileTemplateName = 'Podfile-macos';
  272     } else {
  273       final bool isSwift = (await _xcodeProjectInterpreter.getBuildSettings(
  274         runnerProject.path,
  275       )).containsKey('SWIFT_VERSION');
  276       podfileTemplateName = isSwift ? 'Podfile-ios-swift' : 'Podfile-ios-objc';
  277     }
  278     final File podfileTemplate = _fileSystem.file(_fileSystem.path.join(
  279       Cache.flutterRoot,
  280       'packages',
  281       'flutter_tools',
  282       'templates',
  283       'cocoapods',
  284       podfileTemplateName,
  285     ));
  286     podfileTemplate.copySync(podfile.path);
  287     addPodsDependencyToFlutterXcconfig(xcodeProject);
  288   }
  289 
  290   /// Ensures all `Flutter/Xxx.xcconfig` files for the given Xcode-based
  291   /// sub-project of a parent Flutter project include pods configuration.
  292   void addPodsDependencyToFlutterXcconfig(XcodeBasedProject xcodeProject) {
  293     _addPodsDependencyToFlutterXcconfig(xcodeProject, 'Debug');
  294     _addPodsDependencyToFlutterXcconfig(xcodeProject, 'Release');
  295   }
  296 
  297   void _addPodsDependencyToFlutterXcconfig(XcodeBasedProject xcodeProject, String mode) {
  298     final File file = xcodeProject.xcodeConfigFor(mode);
  299     if (file.existsSync()) {
  300       final String content = file.readAsStringSync();
  301       final String include = '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode
  302           .toLowerCase()}.xcconfig"';
  303       if (!content.contains(include)) {
  304         file.writeAsStringSync('$include\n$content', flush: true);
  305       }
  306     }
  307   }
  308 
  309   /// Ensures that pod install is deemed needed on next check.
  310   void invalidatePodInstallOutput(XcodeBasedProject xcodeProject) {
  311     final File manifestLock = xcodeProject.podManifestLock;
  312     if (manifestLock.existsSync()) {
  313       manifestLock.deleteSync();
  314     }
  315   }
  316 
  317   // Check if you need to run pod install.
  318   // The pod install will run if any of below is true.
  319   // 1. Flutter dependencies have changed
  320   // 2. Podfile.lock doesn't exist or is older than Podfile
  321   // 3. Pods/Manifest.lock doesn't exist (It is deleted when plugins change)
  322   // 4. Podfile.lock doesn't match Pods/Manifest.lock.
  323   bool _shouldRunPodInstall(XcodeBasedProject xcodeProject, bool dependenciesChanged) {
  324     if (dependenciesChanged) {
  325       return true;
  326     }
  327 
  328     final File podfileFile = xcodeProject.podfile;
  329     final File podfileLockFile = xcodeProject.podfileLock;
  330     final File manifestLockFile = xcodeProject.podManifestLock;
  331 
  332     return !podfileLockFile.existsSync()
  333         || !manifestLockFile.existsSync()
  334         || podfileLockFile.statSync().modified.isBefore(podfileFile.statSync().modified)
  335         || podfileLockFile.readAsStringSync() != manifestLockFile.readAsStringSync();
  336   }
  337 
  338   Future<void> _runPodInstall(XcodeBasedProject xcodeProject, String engineDirectory) async {
  339     final Status status = _logger.startProgress('Running pod install...', timeout: _timeoutConfiguration.slowOperation);
  340     final ProcessResult result = await _processManager.run(
  341       <String>['pod', 'install', '--verbose'],
  342       workingDirectory: _fileSystem.path.dirname(xcodeProject.podfile.path),
  343       environment: <String, String>{
  344         'FLUTTER_FRAMEWORK_DIR': engineDirectory,
  345         // See https://github.com/flutter/flutter/issues/10873.
  346         // CocoaPods analytics adds a lot of latency.
  347         'COCOAPODS_DISABLE_STATS': 'true',
  348         'LANG': 'en_US.UTF-8',
  349       },
  350     );
  351     status.stop();
  352     if (_logger.isVerbose || result.exitCode != 0) {
  353       final String stdout = result.stdout as String;
  354       if (stdout.isNotEmpty) {
  355         _logger.printStatus("CocoaPods' output:\n↳");
  356         _logger.printStatus(stdout, indent: 4);
  357       }
  358       final String stderr = result.stderr as String;
  359       if (stderr.isNotEmpty) {
  360         _logger.printStatus('Error output from CocoaPods:\n↳');
  361         _logger.printStatus(stderr, indent: 4);
  362       }
  363     }
  364     if (result.exitCode != 0) {
  365       invalidatePodInstallOutput(xcodeProject);
  366       _diagnosePodInstallFailure(result);
  367       throwToolExit('Error running pod install');
  368     }
  369   }
  370 
  371   void _diagnosePodInstallFailure(ProcessResult result) {
  372     final dynamic stdout = result.stdout;
  373     if (stdout is String && stdout.contains('out-of-date source repos')) {
  374       _logger.printError(
  375         "Error: CocoaPods's specs repository is too out-of-date to satisfy dependencies.\n"
  376         'To update the CocoaPods specs, run:\n'
  377         '  pod repo update\n',
  378         emphasis: true,
  379       );
  380     }
  381   }
  382 
  383   void _warnIfPodfileOutOfDate(XcodeBasedProject xcodeProject) {
  384     if (xcodeProject is! IosProject) {
  385       return;
  386     }
  387 
  388     // Previously, the Podfile created a symlink to the cached artifacts engine framework
  389     // and installed the Flutter pod from that path. This could get out of sync with the copy
  390     // of the Flutter engine that was copied to ios/Flutter by the xcode_backend script.
  391     // It was possible for the symlink to point to a Debug version of the engine when the
  392     // Xcode build configuration was Release, which caused App Store submission rejections.
  393     //
  394     // Warn the user if they are still symlinking to the framework.
  395     final Link flutterSymlink = _fileSystem.link(_fileSystem.path.join(
  396       (xcodeProject as IosProject).symlinks.path,
  397       'flutter',
  398     ));
  399     if (flutterSymlink.existsSync()) {
  400       _logger.printError(
  401         'Warning: Podfile is out of date\n'
  402         '$outOfDateFrameworksPodfileConsequence\n'
  403         'To regenerate the Podfile, run:\n'
  404         '$podfileMigrationInstructions\n',
  405         emphasis: true,
  406       );
  407       return;
  408     }
  409     // Most of the pod and plugin parsing logic was moved from the Podfile
  410     // into the tool's podhelper.rb script. If the Podfile still references
  411     // the old parsed .flutter-plugins file, prompt the regeneration. Old line was:
  412     // plugin_pods = parse_KV_file('../.flutter-plugins')
  413     if (xcodeProject.podfile.existsSync() &&
  414       xcodeProject.podfile.readAsStringSync().contains('.flutter-plugins\'')) {
  415       _logger.printError(
  416         'Warning: Podfile is out of date\n'
  417         '$outOfDatePluginsPodfileConsequence\n'
  418         'To regenerate the Podfile, run:\n'
  419         '$podfileMigrationInstructions\n',
  420         emphasis: true,
  421       );
  422     }
  423   }
  424 }