Support Xcode patch version comparison (#61484)

diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart
index b4b7275..8730ebc 100644
--- a/packages/flutter_tools/lib/src/base/user_messages.dart
+++ b/packages/flutter_tools/lib/src/base/user_messages.dart
@@ -138,8 +138,8 @@
 
   // Messages used in XcodeValidator
   String xcodeLocation(String location) => 'Xcode at $location';
-  String xcodeOutdated(int versionMajor, int versionMinor) =>
-      'Flutter requires a minimum Xcode version of $versionMajor.$versionMinor.0.\n'
+  String xcodeOutdated(int versionMajor, int versionMinor, int versionPatch) =>
+      'Flutter requires a minimum Xcode version of $versionMajor.$versionMinor.$versionPatch.\n'
       'Download the latest version or update via the Mac App Store.';
   String get xcodeEula => "Xcode end user license agreement not signed; open Xcode or run the command 'sudo xcodebuild -license'.";
   String get xcodeMissingSimct =>
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 1223b2e..abaf1d8 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -600,7 +600,7 @@
   final Map<String, String> buildSettings;
 }
 
-const String _xcodeRequirement = 'Xcode $kXcodeRequiredVersionMajor.$kXcodeRequiredVersionMinor or greater is required to develop for iOS.';
+const String _xcodeRequirement = 'Xcode $kXcodeRequiredVersionMajor.$kXcodeRequiredVersionMinor.$kXcodeRequiredVersionPatch or greater is required to develop for iOS.';
 
 bool _checkXcodeVersion() {
   if (!globals.platform.isMacOS) {
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index 8b09b35..7d0c532 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -261,13 +261,15 @@
       return;
     }
     try {
-      final RunResult result = _processUtils.runSync(
-        <String>[_executable, '-version'],
-      );
-      if (result.exitCode != 0) {
-        return;
+      if (_versionText == null) {
+        final RunResult result = _processUtils.runSync(
+          <String>[_executable, '-version'],
+        );
+        if (result.exitCode != 0) {
+          return;
+        }
+        _versionText = result.stdout.trim().replaceAll('\n', ', ');
       }
-      _versionText = result.stdout.trim().replaceAll('\n', ', ');
       final Match match = _versionRegex.firstMatch(versionText);
       if (match == null) {
         return;
@@ -275,7 +277,8 @@
       final String version = match.group(1);
       final List<String> components = version.split('.');
       _majorVersion = int.parse(components[0]);
-      _minorVersion = components.length == 1 ? 0 : int.parse(components[1]);
+      _minorVersion = components.length < 2 ? 0 : int.parse(components[1]);
+      _patchVersion = components.length < 3 ? 0 : int.parse(components[2]);
     } on ProcessException {
       // Ignored, leave values null.
     }
@@ -307,6 +310,14 @@
     return _minorVersion;
   }
 
+  int _patchVersion;
+  int get patchVersion {
+    if (_patchVersion == null) {
+      _updateVersion();
+    }
+    return _patchVersion;
+  }
+
   /// Asynchronously retrieve xcode build settings. This one is preferred for
   /// new call-sites.
   ///
diff --git a/packages/flutter_tools/lib/src/macos/xcode.dart b/packages/flutter_tools/lib/src/macos/xcode.dart
index 81a430c..9d93670 100644
--- a/packages/flutter_tools/lib/src/macos/xcode.dart
+++ b/packages/flutter_tools/lib/src/macos/xcode.dart
@@ -27,6 +27,7 @@
 
 const int kXcodeRequiredVersionMajor = 11;
 const int kXcodeRequiredVersionMinor = 0;
+const int kXcodeRequiredVersionPatch = 0;
 
 enum SdkType {
   iPhone,
@@ -97,8 +98,8 @@
   }
 
   int get majorVersion => _xcodeProjectInterpreter.majorVersion;
-
   int get minorVersion => _xcodeProjectInterpreter.minorVersion;
+  int get patchVersion => _xcodeProjectInterpreter.patchVersion;
 
   String get versionText => _xcodeProjectInterpreter.versionText;
 
@@ -151,6 +152,9 @@
       return true;
     }
     if (majorVersion == kXcodeRequiredVersionMajor) {
+      if (minorVersion == kXcodeRequiredVersionMinor) {
+        return patchVersion >= kXcodeRequiredVersionPatch;
+      }
       return minorVersion >= kXcodeRequiredVersionMinor;
     }
     return false;
diff --git a/packages/flutter_tools/lib/src/macos/xcode_validator.dart b/packages/flutter_tools/lib/src/macos/xcode_validator.dart
index 7aeb5ac..72086ad 100644
--- a/packages/flutter_tools/lib/src/macos/xcode_validator.dart
+++ b/packages/flutter_tools/lib/src/macos/xcode_validator.dart
@@ -39,7 +39,7 @@
       if (!_xcode.isInstalledAndMeetsVersionCheck) {
         xcodeStatus = ValidationType.partial;
         messages.add(ValidationMessage.error(
-          _userMessages.xcodeOutdated(kXcodeRequiredVersionMajor, kXcodeRequiredVersionMinor)
+          _userMessages.xcodeOutdated(kXcodeRequiredVersionMajor, kXcodeRequiredVersionMinor, kXcodeRequiredVersionPatch)
         ));
       }