diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart index b4b7275f07d..8730ebce0fe 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 @@ class UserMessages { // 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 1223b2e3c0e..abaf1d8e89f 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -600,7 +600,7 @@ class XcodeBuildExecution { final Map 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 8b09b35c2ef..7d0c532a95a 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart @@ -261,13 +261,15 @@ class XcodeProjectInterpreter { return; } try { - final RunResult result = _processUtils.runSync( - [_executable, '-version'], - ); - if (result.exitCode != 0) { - return; + if (_versionText == null) { + final RunResult result = _processUtils.runSync( + [_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 @@ class XcodeProjectInterpreter { final String version = match.group(1); final List 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 @@ class XcodeProjectInterpreter { 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 81a430cf93b..9d936709376 100644 --- a/packages/flutter_tools/lib/src/macos/xcode.dart +++ b/packages/flutter_tools/lib/src/macos/xcode.dart @@ -27,6 +27,7 @@ import '../reporting/reporting.dart'; const int kXcodeRequiredVersionMajor = 11; const int kXcodeRequiredVersionMinor = 0; +const int kXcodeRequiredVersionPatch = 0; enum SdkType { iPhone, @@ -97,8 +98,8 @@ class Xcode { } int get majorVersion => _xcodeProjectInterpreter.majorVersion; - int get minorVersion => _xcodeProjectInterpreter.minorVersion; + int get patchVersion => _xcodeProjectInterpreter.patchVersion; String get versionText => _xcodeProjectInterpreter.versionText; @@ -151,6 +152,9 @@ class Xcode { 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 7aeb5acbb9b..72086adfc0a 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 @@ class XcodeValidator extends DoctorValidator { if (!_xcode.isInstalledAndMeetsVersionCheck) { xcodeStatus = ValidationType.partial; messages.add(ValidationMessage.error( - _userMessages.xcodeOutdated(kXcodeRequiredVersionMajor, kXcodeRequiredVersionMinor) + _userMessages.xcodeOutdated(kXcodeRequiredVersionMajor, kXcodeRequiredVersionMinor, kXcodeRequiredVersionPatch) )); } diff --git a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart index e64779e7814..09278a713c3 100644 --- a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart @@ -136,49 +136,38 @@ void main() { expect(fakeProcessManager.hasRemainingExpectations, isFalse); }); - testWithoutContext('xcodebuild majorVersion returns major version', () { + testWithoutContext('xcodebuild version parts can be parsed', () { fakeProcessManager.addCommand(const FakeCommand( command: [xcodebuild, '-version'], stdout: 'Xcode 11.4.1\nBuild version 11N111s', )); expect(xcodeProjectInterpreter.majorVersion, 11); + expect(xcodeProjectInterpreter.minorVersion, 4); + expect(xcodeProjectInterpreter.patchVersion, 1); expect(fakeProcessManager.hasRemainingExpectations, isFalse); }); - testWithoutContext('xcodebuild majorVersion is null when version has unexpected format', () { + testWithoutContext('xcodebuild minor and patch version default to 0', () { + fakeProcessManager.addCommand(const FakeCommand( + command: [xcodebuild, '-version'], + stdout: 'Xcode 11\nBuild version 11N111s', + )); + + expect(xcodeProjectInterpreter.majorVersion, 11); + expect(xcodeProjectInterpreter.minorVersion, 0); + expect(xcodeProjectInterpreter.patchVersion, 0); + expect(fakeProcessManager.hasRemainingExpectations, isFalse); + }); + + testWithoutContext('xcodebuild version parts is null when version has unexpected format', () { fakeProcessManager.addCommand(const FakeCommand( command: [xcodebuild, '-version'], stdout: 'Xcode Ultra5000\nBuild version 8E3004b', )); expect(xcodeProjectInterpreter.majorVersion, isNull); - expect(fakeProcessManager.hasRemainingExpectations, isFalse); - }); - - testWithoutContext('xcodebuild minorVersion returns minor version', () { - fakeProcessManager.addCommand(const FakeCommand( - command: [xcodebuild, '-version'], - stdout: 'Xcode 8.3.3\nBuild version 8E3004b', - )); - expect(xcodeProjectInterpreter.minorVersion, 3); - expect(fakeProcessManager.hasRemainingExpectations, isFalse); - }); - - testWithoutContext('xcodebuild minorVersion returns 0 when minor version is unspecified', () { - fakeProcessManager.addCommand(const FakeCommand( - command: [xcodebuild, '-version'], - stdout: 'Xcode 8\nBuild version 8E3004b', - )); - expect(xcodeProjectInterpreter.minorVersion, 0); - expect(fakeProcessManager.hasRemainingExpectations, isFalse); - }); - - testWithoutContext('xcodebuild minorVersion is null when version has unexpected format', () { - fakeProcessManager.addCommand(const FakeCommand( - command: [xcodebuild, '-version'], - stdout: 'Xcode Ultra5000\nBuild version 8E3004b', - )); expect(xcodeProjectInterpreter.minorVersion, isNull); + expect(xcodeProjectInterpreter.patchVersion, isNull); expect(fakeProcessManager.hasRemainingExpectations, isFalse); }); diff --git a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart index 4f9008c7152..b7ad10c43a5 100644 --- a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart @@ -157,6 +157,7 @@ void main() { when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true); when(mockXcodeProjectInterpreter.majorVersion).thenReturn(9); when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0); + when(mockXcodeProjectInterpreter.patchVersion).thenReturn(0); expect(xcode.isVersionSatisfactory, isFalse); }); @@ -171,6 +172,7 @@ void main() { when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true); when(mockXcodeProjectInterpreter.majorVersion).thenReturn(11); when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0); + when(mockXcodeProjectInterpreter.patchVersion).thenReturn(0); expect(xcode.isVersionSatisfactory, isTrue); }); @@ -179,6 +181,7 @@ void main() { when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true); when(mockXcodeProjectInterpreter.majorVersion).thenReturn(12); when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0); + when(mockXcodeProjectInterpreter.patchVersion).thenReturn(0); expect(xcode.isVersionSatisfactory, isTrue); }); @@ -187,6 +190,16 @@ void main() { when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true); when(mockXcodeProjectInterpreter.majorVersion).thenReturn(11); when(mockXcodeProjectInterpreter.minorVersion).thenReturn(3); + when(mockXcodeProjectInterpreter.patchVersion).thenReturn(0); + + expect(xcode.isVersionSatisfactory, isTrue); + }); + + testWithoutContext('xcodeVersionSatisfactory is true when patch version exceeds minimum', () { + when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true); + when(mockXcodeProjectInterpreter.majorVersion).thenReturn(11); + when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0); + when(mockXcodeProjectInterpreter.patchVersion).thenReturn(1); expect(xcode.isVersionSatisfactory, isTrue); }); @@ -219,6 +232,7 @@ void main() { when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true); when(mockXcodeProjectInterpreter.majorVersion).thenReturn(11); when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0); + when(mockXcodeProjectInterpreter.patchVersion).thenReturn(0); expect(xcode.isInstalledAndMeetsVersionCheck, isFalse); expect(fakeProcessManager.hasRemainingExpectations, isFalse); @@ -233,6 +247,7 @@ void main() { when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true); when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10); when(mockXcodeProjectInterpreter.minorVersion).thenReturn(2); + when(mockXcodeProjectInterpreter.patchVersion).thenReturn(0); expect(xcode.isInstalledAndMeetsVersionCheck, isFalse); expect(fakeProcessManager.hasRemainingExpectations, isFalse); @@ -247,6 +262,7 @@ void main() { when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true); when(mockXcodeProjectInterpreter.majorVersion).thenReturn(11); when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0); + when(mockXcodeProjectInterpreter.patchVersion).thenReturn(0); expect(xcode.isInstalledAndMeetsVersionCheck, isTrue); expect(fakeProcessManager.hasRemainingExpectations, isFalse); diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index efd0385f1cf..64cf97f6c59 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart @@ -394,6 +394,9 @@ class FakeXcodeProjectInterpreter implements XcodeProjectInterpreter { @override int get minorVersion => 0; + @override + int get patchVersion => 0; + @override Future> getBuildSettings( String projectPath, {