Unverified Commit d1e0273e authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

refactor cocoapods validator to detect broken install (#38560)

parent b3014ff5
...@@ -172,6 +172,11 @@ class UserMessages { ...@@ -172,6 +172,11 @@ class UserMessages {
'$consequence\n' '$consequence\n'
'To upgrade:\n' 'To upgrade:\n'
'$upgradeInstructions'; '$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 // Messages used in VsCodeValidator
String vsCodeVersion(String version) => 'version $version'; String vsCodeVersion(String version) => 'version $version';
......
...@@ -29,6 +29,11 @@ const String unknownCocoaPodsConsequence = ''' ...@@ -29,6 +29,11 @@ const String unknownCocoaPodsConsequence = '''
Flutter is unable to determine the installed CocoaPods's version. 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.'''; 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 = ''' const String cocoaPodsInstallInstructions = '''
sudo gem install cocoapods sudo gem install cocoapods
pod setup'''; pod setup''';
...@@ -52,6 +57,8 @@ enum CocoaPodsStatus { ...@@ -52,6 +57,8 @@ enum CocoaPodsStatus {
belowRecommendedVersion, belowRecommendedVersion,
/// Everything should be fine. /// Everything should be fine.
recommended, recommended,
/// iOS plugins will not work, re-install required.
brokenInstall,
} }
class CocoaPods { class CocoaPods {
...@@ -60,6 +67,8 @@ class CocoaPods { ...@@ -60,6 +67,8 @@ class CocoaPods {
String get cocoaPodsMinimumVersion => '1.6.0'; String get cocoaPodsMinimumVersion => '1.6.0';
String get cocoaPodsRecommendedVersion => '1.6.0'; String get cocoaPodsRecommendedVersion => '1.6.0';
Future<bool> get isInstalled => exitsHappyAsync(<String>['which', 'pod']);
Future<String> get cocoaPodsVersionText { Future<String> get cocoaPodsVersionText {
_versionText ??= runAsync(<String>['pod', '--version']).then<String>((RunResult result) { _versionText ??= runAsync(<String>['pod', '--version']).then<String>((RunResult result) {
return result.exitCode == 0 ? result.stdout.trim() : null; return result.exitCode == 0 ? result.stdout.trim() : null;
...@@ -68,9 +77,13 @@ class CocoaPods { ...@@ -68,9 +77,13 @@ class CocoaPods {
} }
Future<CocoaPodsStatus> get evaluateCocoaPodsInstallation async { Future<CocoaPodsStatus> get evaluateCocoaPodsInstallation async {
final String versionText = await cocoaPodsVersionText; if (!(await isInstalled)) {
if (versionText == null)
return CocoaPodsStatus.notInstalled; return CocoaPodsStatus.notInstalled;
}
final String versionText = await cocoaPodsVersionText;
if (versionText == null) {
return CocoaPodsStatus.brokenInstall;
}
try { try {
final Version installedVersion = Version.parse(versionText); final Version installedVersion = Version.parse(versionText);
if (installedVersion == null) if (installedVersion == null)
......
...@@ -34,6 +34,11 @@ class CocoaPodsValidator extends DoctorValidator { ...@@ -34,6 +34,11 @@ class CocoaPodsValidator extends DoctorValidator {
status = ValidationType.missing; status = ValidationType.missing;
messages.add(ValidationMessage.error( messages.add(ValidationMessage.error(
userMessages.cocoaPodsMissing(noCocoaPodsConsequence, cocoaPodsInstallInstructions))); 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) { } else if (cocoaPodsStatus == CocoaPodsStatus.unknownVersion) {
status = ValidationType.partial; status = ValidationType.partial;
messages.add(ValidationMessage.hint( messages.add(ValidationMessage.hint(
......
...@@ -30,10 +30,6 @@ void main() { ...@@ -30,10 +30,6 @@ void main() {
CocoaPods cocoaPodsUnderTest; CocoaPods cocoaPodsUnderTest;
InvokeProcess resultOfPodVersion; InvokeProcess resultOfPodVersion;
void pretendPodIsNotInstalled() {
resultOfPodVersion = () async => throw 'Executable does not exist';
}
void pretendPodVersionFails() { void pretendPodVersionFails() {
resultOfPodVersion = () async => exitsWithError(); resultOfPodVersion = () async => exitsWithError();
} }
...@@ -93,6 +89,22 @@ void main() { ...@@ -93,6 +89,22 @@ void main() {
)).thenAnswer((_) async => exitsHappy()); )).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', () { group('Evaluate installation', () {
testUsingContext('detects not installed, if pod exec does not exist', () async { testUsingContext('detects not installed, if pod exec does not exist', () async {
pretendPodIsNotInstalled(); pretendPodIsNotInstalled();
...@@ -101,14 +113,16 @@ void main() { ...@@ -101,14 +113,16 @@ void main() {
ProcessManager: () => mockProcessManager, 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(); pretendPodVersionFails();
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.notInstalled); expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.brokenInstall);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
}); });
testUsingContext('detects installed', () async { testUsingContext('detects installed', () async {
pretendPodIsInstalled();
pretendPodVersionIs('0.0.1'); pretendPodVersionIs('0.0.1');
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, isNot(CocoaPodsStatus.notInstalled)); expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, isNot(CocoaPodsStatus.notInstalled));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -116,6 +130,7 @@ void main() { ...@@ -116,6 +130,7 @@ void main() {
}); });
testUsingContext('detects unknown version', () async { testUsingContext('detects unknown version', () async {
pretendPodIsInstalled();
pretendPodVersionIs('Plugin loaded.\n1.5.3'); pretendPodVersionIs('Plugin loaded.\n1.5.3');
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.unknownVersion); expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.unknownVersion);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -123,6 +138,7 @@ void main() { ...@@ -123,6 +138,7 @@ void main() {
}); });
testUsingContext('detects below minimum version', () async { testUsingContext('detects below minimum version', () async {
pretendPodIsInstalled();
pretendPodVersionIs('1.5.0'); pretendPodVersionIs('1.5.0');
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.belowMinimumVersion); expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.belowMinimumVersion);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -130,6 +146,7 @@ void main() { ...@@ -130,6 +146,7 @@ void main() {
}); });
testUsingContext('detects at recommended version', () async { testUsingContext('detects at recommended version', () async {
pretendPodIsInstalled();
pretendPodVersionIs('1.6.0'); pretendPodVersionIs('1.6.0');
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.recommended); expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.recommended);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -137,6 +154,7 @@ void main() { ...@@ -137,6 +154,7 @@ void main() {
}); });
testUsingContext('detects above recommended version', () async { testUsingContext('detects above recommended version', () async {
pretendPodIsInstalled();
pretendPodVersionIs('1.6.1'); pretendPodVersionIs('1.6.1');
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.recommended); expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.recommended);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -279,6 +297,7 @@ void main() { ...@@ -279,6 +297,7 @@ void main() {
}); });
testUsingContext('throws, if Podfile is missing.', () async { testUsingContext('throws, if Podfile is missing.', () async {
pretendPodIsInstalled();
try { try {
await cocoaPodsUnderTest.processPods( await cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios, xcodeProject: projectUnderTest.ios,
...@@ -299,6 +318,7 @@ void main() { ...@@ -299,6 +318,7 @@ void main() {
}); });
testUsingContext('throws, if specs repo is outdated.', () async { testUsingContext('throws, if specs repo is outdated.', () async {
pretendPodIsInstalled();
fs.file(fs.path.join('project', 'ios', 'Podfile')) fs.file(fs.path.join('project', 'ios', 'Podfile'))
..createSync() ..createSync()
..writeAsStringSync('Existing Podfile'); ..writeAsStringSync('Existing Podfile');
...@@ -345,6 +365,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -345,6 +365,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
}); });
testUsingContext('run pod install, if Podfile.lock is missing', () async { testUsingContext('run pod install, if Podfile.lock is missing', () async {
pretendPodIsInstalled();
projectUnderTest.ios.podfile projectUnderTest.ios.podfile
..createSync() ..createSync()
..writeAsStringSync('Existing Podfile'); ..writeAsStringSync('Existing Podfile');
...@@ -368,6 +389,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -368,6 +389,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
}); });
testUsingContext('runs pod install, if Manifest.lock is missing', () async { testUsingContext('runs pod install, if Manifest.lock is missing', () async {
pretendPodIsInstalled();
projectUnderTest.ios.podfile projectUnderTest.ios.podfile
..createSync() ..createSync()
..writeAsStringSync('Existing Podfile'); ..writeAsStringSync('Existing Podfile');
...@@ -394,6 +416,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -394,6 +416,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
}); });
testUsingContext('runs pod install, if Manifest.lock different from Podspec.lock', () async { testUsingContext('runs pod install, if Manifest.lock different from Podspec.lock', () async {
pretendPodIsInstalled();
projectUnderTest.ios.podfile projectUnderTest.ios.podfile
..createSync() ..createSync()
..writeAsStringSync('Existing Podfile'); ..writeAsStringSync('Existing Podfile');
...@@ -423,6 +446,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -423,6 +446,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
}); });
testUsingContext('runs pod install, if flutter framework changed', () async { testUsingContext('runs pod install, if flutter framework changed', () async {
pretendPodIsInstalled();
projectUnderTest.ios.podfile projectUnderTest.ios.podfile
..createSync() ..createSync()
..writeAsStringSync('Existing Podfile'); ..writeAsStringSync('Existing Podfile');
...@@ -452,6 +476,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -452,6 +476,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
}); });
testUsingContext('runs pod install, if Podfile.lock is older than Podfile', () async { testUsingContext('runs pod install, if Podfile.lock is older than Podfile', () async {
pretendPodIsInstalled();
projectUnderTest.ios.podfile projectUnderTest.ios.podfile
..createSync() ..createSync()
..writeAsStringSync('Existing Podfile'); ..writeAsStringSync('Existing Podfile');
...@@ -483,6 +508,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -483,6 +508,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
}); });
testUsingContext('skips pod install, if nothing changed', () async { testUsingContext('skips pod install, if nothing changed', () async {
pretendPodIsInstalled();
projectUnderTest.ios.podfile projectUnderTest.ios.podfile
..createSync() ..createSync()
..writeAsStringSync('Existing Podfile'); ..writeAsStringSync('Existing Podfile');
...@@ -509,6 +535,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -509,6 +535,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
}); });
testUsingContext('a failed pod install deletes Pods/Manifest.lock', () async { testUsingContext('a failed pod install deletes Pods/Manifest.lock', () async {
pretendPodIsInstalled();
projectUnderTest.ios.podfile projectUnderTest.ios.podfile
..createSync() ..createSync()
..writeAsStringSync('Existing Podfile'); ..writeAsStringSync('Existing Podfile');
...@@ -559,6 +586,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -559,6 +586,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
}); });
testUsingContext('succeeds, if specs repo is in CP_REPOS_DIR.', () async { testUsingContext('succeeds, if specs repo is in CP_REPOS_DIR.', () async {
pretendPodIsInstalled();
fs.file(fs.path.join('project', 'ios', 'Podfile')) fs.file(fs.path.join('project', 'ios', 'Podfile'))
..createSync() ..createSync()
..writeAsStringSync('Existing Podfile'); ..writeAsStringSync('Existing Podfile');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment