V1.9.1 hotfixes (#42863)

* refactor cocoapods validator to detect broken install (#38560)
* Skip pod initialization if version >= 1.8.0. (#41491)
diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart
index 1764483..a1f1d91 100644
--- a/packages/flutter_tools/lib/src/base/user_messages.dart
+++ b/packages/flutter_tools/lib/src/base/user_messages.dart
@@ -172,6 +172,11 @@
       '$consequence\n'
       'To upgrade:\n'
       '$upgradeInstructions';
+  String cocoaPodsBrokenInstall(String consequence, String reinstallInstructions) =>
+      'CocoaPods installed but not working.\n'
+      '$consequence\n'
+      'To re-install CocoaPods, run:\n'
+      '$reinstallInstructions';
 
   // Messages used in VsCodeValidator
   String vsCodeVersion(String version) => 'version $version';
diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart
index 9729387..10e5448 100644
--- a/packages/flutter_tools/lib/src/macos/cocoapods.dart
+++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart
@@ -29,13 +29,16 @@
   Flutter is unable to determine the installed CocoaPods's version.
   Ensure that the output of 'pod --version' contains only digits and . to be recognized by Flutter.''';
 
+const String brokenCocoaPodsConsequence = '''
+  You appear to have CocoaPods installed but it is not working.
+  This can happen if the version of Ruby that CocoaPods was installed with is different from the one being used to invoke it.
+  This can usually be fixed by re-installing CocoaPods. For more info, see https://github.com/flutter/flutter/issues/14293.''';
+
 const String cocoaPodsInstallInstructions = '''
-  sudo gem install cocoapods
-  pod setup''';
+  sudo gem install cocoapods''';
 
 const String cocoaPodsUpgradeInstructions = '''
-  sudo gem install cocoapods
-  pod setup''';
+  sudo gem install cocoapods''';
 
 CocoaPods get cocoaPods => context.get<CocoaPods>();
 
@@ -52,6 +55,8 @@
   belowRecommendedVersion,
   /// Everything should be fine.
   recommended,
+  /// iOS plugins will not work, re-install required.
+  brokenInstall,
 }
 
 class CocoaPods {
@@ -60,6 +65,8 @@
   String get cocoaPodsMinimumVersion => '1.6.0';
   String get cocoaPodsRecommendedVersion => '1.6.0';
 
+  Future<bool> get isInstalled => exitsHappyAsync(<String>['which', 'pod']);
+
   Future<String> get cocoaPodsVersionText {
     _versionText ??= runAsync(<String>['pod', '--version']).then<String>((RunResult result) {
       return result.exitCode == 0 ? result.stdout.trim() : null;
@@ -68,9 +75,13 @@
   }
 
   Future<CocoaPodsStatus> get evaluateCocoaPodsInstallation async {
-    final String versionText = await cocoaPodsVersionText;
-    if (versionText == null)
+    if (!(await isInstalled)) {
       return CocoaPodsStatus.notInstalled;
+    }
+    final String versionText = await cocoaPodsVersionText;
+    if (versionText == null) {
+      return CocoaPodsStatus.brokenInstall;
+    }
     try {
       final Version installedVersion = Version.parse(versionText);
       if (installedVersion == null)
@@ -89,12 +100,20 @@
   /// Whether CocoaPods ran 'pod setup' once where the costly pods' specs are
   /// cloned.
   ///
+  /// Versions >= 1.8.0 do not require 'pod setup' and default to a CDN instead
+  /// of a locally cloned repository.
+  /// See http://blog.cocoapods.org/CocoaPods-1.8.0-beta/
+  ///
   /// A user can override the default location via the CP_REPOS_DIR environment
   /// variable.
   ///
   /// See https://github.com/CocoaPods/CocoaPods/blob/master/lib/cocoapods/config.rb#L138
   /// for details of this variable.
-  Future<bool> get isCocoaPodsInitialized {
+  Future<bool> get isCocoaPodsInitialized async {
+    final Version installedVersion = Version.parse(await cocoaPodsVersionText);
+    if (installedVersion != null && installedVersion >= Version.parse('1.8.0')) {
+      return true;
+    }
     final String cocoapodsReposDir = platform.environment['CP_REPOS_DIR'] ?? fs.path.join(homeDirPath, '.cocoapods', 'repos');
     return fs.isDirectory(fs.path.join(cocoapodsReposDir, 'master'));
   }
diff --git a/packages/flutter_tools/lib/src/macos/cocoapods_validator.dart b/packages/flutter_tools/lib/src/macos/cocoapods_validator.dart
index aaee745..ce57632 100644
--- a/packages/flutter_tools/lib/src/macos/cocoapods_validator.dart
+++ b/packages/flutter_tools/lib/src/macos/cocoapods_validator.dart
@@ -34,6 +34,11 @@
         status = ValidationType.missing;
         messages.add(ValidationMessage.error(
             userMessages.cocoaPodsMissing(noCocoaPodsConsequence, cocoaPodsInstallInstructions)));
+      } else if (cocoaPodsStatus == CocoaPodsStatus.brokenInstall) {
+        status = ValidationType.missing;
+        messages.add(ValidationMessage.error(
+            userMessages.cocoaPodsBrokenInstall(brokenCocoaPodsConsequence, cocoaPodsInstallInstructions)));
+
       } else if (cocoaPodsStatus == CocoaPodsStatus.unknownVersion) {
         status = ValidationType.partial;
         messages.add(ValidationMessage.hint(
diff --git a/packages/flutter_tools/templates/cocoapods/Podfile-ios-objc b/packages/flutter_tools/templates/cocoapods/Podfile-ios-objc
index 1de1efc..64ba749 100644
--- a/packages/flutter_tools/templates/cocoapods/Podfile-ios-objc
+++ b/packages/flutter_tools/templates/cocoapods/Podfile-ios-objc
@@ -1,6 +1,3 @@
-# Using a CDN with CocoaPods 1.7.2 or later can save a lot of time on pod installation, but it's experimental rather than the default.
-# source 'https://cdn.cocoapods.org/'
-
 # Uncomment this line to define a global platform for your project
 # platform :ios, '9.0'
 
diff --git a/packages/flutter_tools/templates/cocoapods/Podfile-ios-swift b/packages/flutter_tools/templates/cocoapods/Podfile-ios-swift
index e5e1440..e9286cb 100644
--- a/packages/flutter_tools/templates/cocoapods/Podfile-ios-swift
+++ b/packages/flutter_tools/templates/cocoapods/Podfile-ios-swift
@@ -1,6 +1,3 @@
-# Using a CDN with CocoaPods 1.7.2 or later can save a lot of time on pod installation, but it's experimental rather than the default.
-# source 'https://cdn.cocoapods.org/'
-
 # Uncomment this line to define a global platform for your project
 # platform :ios, '9.0'
 
diff --git a/packages/flutter_tools/templates/cocoapods/Podfile-macos b/packages/flutter_tools/templates/cocoapods/Podfile-macos
index 23f5c12..6bb04df 100644
--- a/packages/flutter_tools/templates/cocoapods/Podfile-macos
+++ b/packages/flutter_tools/templates/cocoapods/Podfile-macos
@@ -1,6 +1,3 @@
-# Using a CDN with CocoaPods 1.7.2 or later can save a lot of time on pod installation, but it's experimental rather than the default.
-# source 'https://cdn.cocoapods.org/'
-
 platform :osx, '10.11'
 
 # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
diff --git a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart
index 068b87a..dda5340 100644
--- a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart
+++ b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart
@@ -30,10 +30,6 @@
   CocoaPods cocoaPodsUnderTest;
   InvokeProcess resultOfPodVersion;
 
-  void pretendPodIsNotInstalled() {
-    resultOfPodVersion = () async => throw 'Executable does not exist';
-  }
-
   void pretendPodVersionFails() {
     resultOfPodVersion = () async => exitsWithError();
   }
@@ -93,6 +89,22 @@
     )).thenAnswer((_) async => exitsHappy());
   });
 
+  void pretendPodIsNotInstalled() {
+    when(mockProcessManager.run(
+      <String>['which', 'pod'],
+      workingDirectory: anyNamed('workingDirectory'),
+      environment: anyNamed('environment'),
+    )).thenAnswer((_) async => exitsWithError());
+  }
+
+  void pretendPodIsInstalled() {
+    when(mockProcessManager.run(
+      <String>['which', 'pod'],
+      workingDirectory: anyNamed('workingDirectory'),
+      environment: anyNamed('environment'),
+    )).thenAnswer((_) async => exitsHappy());
+  }
+
   group('Evaluate installation', () {
     testUsingContext('detects not installed, if pod exec does not exist', () async {
       pretendPodIsNotInstalled();
@@ -101,14 +113,16 @@
       ProcessManager: () => mockProcessManager,
     });
 
-    testUsingContext('detects not installed, if pod version fails', () async {
+    testUsingContext('detects not installed, if pod is installed but version fails', () async {
+      pretendPodIsInstalled();
       pretendPodVersionFails();
-      expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.notInstalled);
+      expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.brokenInstall);
     }, overrides: <Type, Generator>{
       ProcessManager: () => mockProcessManager,
     });
 
     testUsingContext('detects installed', () async {
+      pretendPodIsInstalled();
       pretendPodVersionIs('0.0.1');
       expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, isNot(CocoaPodsStatus.notInstalled));
     }, overrides: <Type, Generator>{
@@ -116,6 +130,7 @@
     });
 
     testUsingContext('detects unknown version', () async {
+      pretendPodIsInstalled();
       pretendPodVersionIs('Plugin loaded.\n1.5.3');
       expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.unknownVersion);
     }, overrides: <Type, Generator>{
@@ -123,6 +138,7 @@
     });
 
     testUsingContext('detects below minimum version', () async {
+      pretendPodIsInstalled();
       pretendPodVersionIs('1.5.0');
       expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.belowMinimumVersion);
     }, overrides: <Type, Generator>{
@@ -130,6 +146,7 @@
     });
 
     testUsingContext('detects at recommended version', () async {
+      pretendPodIsInstalled();
       pretendPodVersionIs('1.6.0');
       expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.recommended);
     }, overrides: <Type, Generator>{
@@ -137,11 +154,22 @@
     });
 
     testUsingContext('detects above recommended version', () async {
+      pretendPodIsInstalled();
       pretendPodVersionIs('1.6.1');
       expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.recommended);
     }, overrides: <Type, Generator>{
       ProcessManager: () => mockProcessManager,
     });
+
+    testUsingContext('detects initialized over 1.8.0', () async {
+      pretendPodIsInstalled();
+      pretendPodVersionIs('1.8.0');
+      expect(await cocoaPodsUnderTest.isCocoaPodsInitialized, isTrue);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Platform: () => FakePlatform(),
+      FileSystem: () => fs,
+    });
   });
 
   group('Setup Podfile', () {
@@ -279,6 +307,7 @@
     });
 
     testUsingContext('throws, if Podfile is missing.', () async {
+      pretendPodIsInstalled();
       try {
         await cocoaPodsUnderTest.processPods(
           xcodeProject: projectUnderTest.ios,
@@ -299,6 +328,7 @@
     });
 
     testUsingContext('throws, if specs repo is outdated.', () async {
+      pretendPodIsInstalled();
       fs.file(fs.path.join('project', 'ios', 'Podfile'))
         ..createSync()
         ..writeAsStringSync('Existing Podfile');
@@ -345,6 +375,7 @@
     });
 
     testUsingContext('run pod install, if Podfile.lock is missing', () async {
+      pretendPodIsInstalled();
       projectUnderTest.ios.podfile
         ..createSync()
         ..writeAsStringSync('Existing Podfile');
@@ -368,6 +399,7 @@
     });
 
     testUsingContext('runs pod install, if Manifest.lock is missing', () async {
+      pretendPodIsInstalled();
       projectUnderTest.ios.podfile
         ..createSync()
         ..writeAsStringSync('Existing Podfile');
@@ -394,6 +426,7 @@
     });
 
     testUsingContext('runs pod install, if Manifest.lock different from Podspec.lock', () async {
+      pretendPodIsInstalled();
       projectUnderTest.ios.podfile
         ..createSync()
         ..writeAsStringSync('Existing Podfile');
@@ -423,6 +456,7 @@
     });
 
     testUsingContext('runs pod install, if flutter framework changed', () async {
+      pretendPodIsInstalled();
       projectUnderTest.ios.podfile
         ..createSync()
         ..writeAsStringSync('Existing Podfile');
@@ -452,6 +486,7 @@
     });
 
     testUsingContext('runs pod install, if Podfile.lock is older than Podfile', () async {
+      pretendPodIsInstalled();
       projectUnderTest.ios.podfile
         ..createSync()
         ..writeAsStringSync('Existing Podfile');
@@ -483,6 +518,7 @@
     });
 
     testUsingContext('skips pod install, if nothing changed', () async {
+      pretendPodIsInstalled();
       projectUnderTest.ios.podfile
         ..createSync()
         ..writeAsStringSync('Existing Podfile');
@@ -509,6 +545,7 @@
     });
 
     testUsingContext('a failed pod install deletes Pods/Manifest.lock', () async {
+      pretendPodIsInstalled();
       projectUnderTest.ios.podfile
         ..createSync()
         ..writeAsStringSync('Existing Podfile');
@@ -559,6 +596,7 @@
     });
 
     testUsingContext('succeeds, if specs repo is in CP_REPOS_DIR.', () async {
+      pretendPodIsInstalled();
       fs.file(fs.path.join('project', 'ios', 'Podfile'))
         ..createSync()
         ..writeAsStringSync('Existing Podfile');