refactor cocoapods validator to detect broken install (#38560)
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 ca2577f..ac006fa 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapods.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart
@@ -29,6 +29,11 @@ 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'''; @@ -52,6 +57,8 @@ belowRecommendedVersion, /// Everything should be fine. recommended, + /// iOS plugins will not work, re-install required. + brokenInstall, } class CocoaPods { @@ -60,6 +67,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 +77,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)
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/test/general.shard/macos/cocoapods_test.dart b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart index 068b87a..6ebccdb 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,6 +154,7 @@ }); testUsingContext('detects above recommended version', () async { + pretendPodIsInstalled(); pretendPodVersionIs('1.6.1'); expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.recommended); }, overrides: <Type, Generator>{ @@ -279,6 +297,7 @@ }); testUsingContext('throws, if Podfile is missing.', () async { + pretendPodIsInstalled(); try { await cocoaPodsUnderTest.processPods( xcodeProject: projectUnderTest.ios, @@ -299,6 +318,7 @@ }); testUsingContext('throws, if specs repo is outdated.', () async { + pretendPodIsInstalled(); fs.file(fs.path.join('project', 'ios', 'Podfile')) ..createSync() ..writeAsStringSync('Existing Podfile'); @@ -345,6 +365,7 @@ }); testUsingContext('run pod install, if Podfile.lock is missing', () async { + pretendPodIsInstalled(); projectUnderTest.ios.podfile ..createSync() ..writeAsStringSync('Existing Podfile'); @@ -368,6 +389,7 @@ }); testUsingContext('runs pod install, if Manifest.lock is missing', () async { + pretendPodIsInstalled(); projectUnderTest.ios.podfile ..createSync() ..writeAsStringSync('Existing Podfile'); @@ -394,6 +416,7 @@ }); testUsingContext('runs pod install, if Manifest.lock different from Podspec.lock', () async { + pretendPodIsInstalled(); projectUnderTest.ios.podfile ..createSync() ..writeAsStringSync('Existing Podfile'); @@ -423,6 +446,7 @@ }); testUsingContext('runs pod install, if flutter framework changed', () async { + pretendPodIsInstalled(); projectUnderTest.ios.podfile ..createSync() ..writeAsStringSync('Existing Podfile'); @@ -452,6 +476,7 @@ }); testUsingContext('runs pod install, if Podfile.lock is older than Podfile', () async { + pretendPodIsInstalled(); projectUnderTest.ios.podfile ..createSync() ..writeAsStringSync('Existing Podfile'); @@ -483,6 +508,7 @@ }); testUsingContext('skips pod install, if nothing changed', () async { + pretendPodIsInstalled(); projectUnderTest.ios.podfile ..createSync() ..writeAsStringSync('Existing Podfile'); @@ -509,6 +535,7 @@ }); testUsingContext('a failed pod install deletes Pods/Manifest.lock', () async { + pretendPodIsInstalled(); projectUnderTest.ios.podfile ..createSync() ..writeAsStringSync('Existing Podfile'); @@ -559,6 +586,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');