Skip to content

Commit d628a6f

Browse files
Give an actionable error message when a Pod requires a higher minimum OS version (#138097)
Checks `pod install` output for the case where a pod requires a higher minimum OS deployment target version than the app is set to use, and attempts to turn it into a more actionable error message. This isn't foolproof since we are parsing the Ruby rather than actually executing it, but I would expect that the vast majority of cases would end up in the most useful version (and even those that don't are still much clearer with this as the final error message text than without it). Fixes flutter/flutter#113762
1 parent 311193d commit d628a6f

File tree

2 files changed

+615
-2
lines changed

2 files changed

+615
-2
lines changed

packages/flutter_tools/lib/src/macos/cocoapods.dart

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ class CocoaPods {
355355

356356
if (result.exitCode != 0) {
357357
invalidatePodInstallOutput(xcodeProject);
358-
_diagnosePodInstallFailure(result);
358+
_diagnosePodInstallFailure(result, xcodeProject);
359359
throwToolExit('Error running pod install');
360360
} else if (xcodeProject.podfileLock.existsSync()) {
361361
// Even if the Podfile.lock didn't change, update its modified date to now
@@ -367,7 +367,7 @@ class CocoaPods {
367367
}
368368
}
369369

370-
void _diagnosePodInstallFailure(ProcessResult result) {
370+
void _diagnosePodInstallFailure(ProcessResult result, XcodeBasedProject xcodeProject) {
371371
final Object? stdout = result.stdout;
372372
final Object? stderr = result.stderr;
373373
if (stdout is! String || stderr is! String) {
@@ -397,7 +397,130 @@ class CocoaPods {
397397
' sudo gem uninstall ffi && sudo gem install ffi -- --enable-libffi-alloc\n',
398398
emphasis: true,
399399
);
400+
} else if (stdout.contains('required a higher minimum deployment target')) {
401+
final ({String failingPod, String sourcePlugin, String podPluginSubdir})?
402+
podInfo = _parseMinDeploymentFailureInfo(stdout);
403+
if (podInfo != null) {
404+
final String sourcePlugin = podInfo.sourcePlugin;
405+
// If the plugin's podfile has set its own minimum version correctly
406+
// based on the requirements of its dependencies the failing pod should
407+
// be the plugin itself, but if not they may be different (e.g., if
408+
// a plugin says its minimum iOS version is 11, but depends on a pod
409+
// with a minimum version of 12, then building for 11 will report that
410+
// pod as failing.)
411+
if (podInfo.failingPod == podInfo.sourcePlugin) {
412+
final Directory symlinksDir;
413+
final String podPlatformString;
414+
final String platformName;
415+
final String docsLink;
416+
if (xcodeProject is IosProject) {
417+
symlinksDir = xcodeProject.symlinks;
418+
podPlatformString = 'ios';
419+
platformName = 'iOS';
420+
docsLink = 'https://docs.flutter.dev/deployment/ios';
421+
} else if (xcodeProject is MacOSProject) {
422+
symlinksDir = xcodeProject.ephemeralDirectory.childDirectory('.symlinks');
423+
podPlatformString = 'osx';
424+
platformName = 'macOS';
425+
docsLink = 'https://docs.flutter.dev/deployment/macos';
426+
} else {
427+
return;
428+
}
429+
final File podspec = symlinksDir
430+
.childDirectory('plugins')
431+
.childDirectory(sourcePlugin)
432+
.childDirectory(podInfo.podPluginSubdir)
433+
.childFile('$sourcePlugin.podspec');
434+
final String? minDeploymentVersion = _findPodspecMinDeploymentVersion(
435+
podspec,
436+
podPlatformString
437+
);
438+
if (minDeploymentVersion != null) {
439+
_logger.printError(
440+
'Error: The plugin "$sourcePlugin" requires a higher minimum '
441+
'$platformName deployment version than your application is targeting.\n'
442+
"To build, increase your application's deployment target to at "
443+
'least $minDeploymentVersion as described at $docsLink',
444+
emphasis: true,
445+
);
446+
} else {
447+
// If for some reason the min version can't be parsed out, provide
448+
// a less specific error message that still describes the problem,
449+
// but also requests filing a Flutter issue so the parsing in
450+
// _findPodspecMinDeploymentVersion can be improved.
451+
_logger.printError(
452+
'Error: The plugin "$sourcePlugin" requires a higher minimum '
453+
'$platformName deployment version than your application is targeting.\n'
454+
"To build, increase your application's deployment target as "
455+
'described at $docsLink\n\n'
456+
'The minimum required version for "$sourcePlugin" could not be '
457+
'determined. Please file an issue at '
458+
'https://github.com/flutter/flutter/issues about this error message.',
459+
emphasis: true,
460+
);
461+
}
462+
} else {
463+
// In theory this could find the failing pod's spec and parse out its
464+
// minimum deployment version, but finding that spec would add a lot
465+
// of complexity to handle a case that plugin authors should not
466+
// create, so this just provides the actionable step of following up
467+
// with the plugin developer.
468+
_logger.printError(
469+
'Error: The pod "${podInfo.failingPod}" required by the plugin '
470+
'"$sourcePlugin" requires a higher minimum iOS deployment version '
471+
"than the plugin's reported minimum version.\n"
472+
'To build, remove the plugin "$sourcePlugin", or contact the plugin\'s '
473+
'developers for assistance.',
474+
emphasis: true,
475+
);
476+
}
477+
}
478+
}
479+
}
480+
481+
({String failingPod, String sourcePlugin, String podPluginSubdir})?
482+
_parseMinDeploymentFailureInfo(String podInstallOutput) {
483+
final RegExp sourceLine = RegExp(r'\(from `.*\.symlinks/plugins/([^/]+)/([^/]+)`\)');
484+
final RegExp dependencyLine = RegExp(r'Specs satisfying the `([^ ]+).*` dependency were found, '
485+
'but they required a higher minimum deployment target');
486+
final RegExpMatch? sourceMatch = sourceLine.firstMatch(podInstallOutput);
487+
final RegExpMatch? dependencyMatch = dependencyLine.firstMatch(podInstallOutput);
488+
if (sourceMatch == null || dependencyMatch == null) {
489+
return null;
490+
}
491+
return (
492+
failingPod: dependencyMatch.group(1)!,
493+
sourcePlugin: sourceMatch.group(1)!,
494+
podPluginSubdir: sourceMatch.group(2)!
495+
);
496+
}
497+
498+
String? _findPodspecMinDeploymentVersion(File podspec, String platformString) {
499+
if (!podspec.existsSync()) {
500+
return null;
400501
}
502+
// There are two ways the deployment target can be specified; see
503+
// https://guides.cocoapods.org/syntax/podspec.html#group_platform
504+
final RegExp platformPattern = RegExp(
505+
// Example: spec.platform = :osx, '10.8'
506+
// where "spec" is an arbitrary variable name.
507+
r'^\s*[a-zA-Z_]+\.platform\s*=\s*'
508+
':$platformString'
509+
r'''\s*,\s*["']([^"']+)["']''',
510+
multiLine: true
511+
);
512+
final RegExp deploymentTargetPlatform = RegExp(
513+
// Example: spec.osx.deployment_target = '10.8'
514+
// where "spec" is an arbitrary variable name.
515+
r'^\s*[a-zA-Z_]+\.'
516+
'$platformString\\.deployment_target'
517+
r'''\s*=\s*["']([^"']+)["']''',
518+
multiLine: true
519+
);
520+
final String podspecContents = podspec.readAsStringSync();
521+
final RegExpMatch? match = platformPattern.firstMatch(podspecContents) ??
522+
deploymentTargetPlatform.firstMatch(podspecContents);
523+
return match?.group(1);
401524
}
402525

403526
bool _isFfiX86Error(String error) {

0 commit comments

Comments
 (0)