diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart index 6a1ac13b9b..bb951f7e1d 100644 --- a/packages/flutter_tools/lib/src/commands/build_aot.dart +++ b/packages/flutter_tools/lib/src/commands/build_aot.dart @@ -11,6 +11,7 @@ import '../base/context.dart'; import '../base/file_system.dart'; import '../base/logger.dart'; import '../base/process.dart'; +import '../base/version.dart'; import '../build_info.dart'; import '../dart/package_map.dart'; import '../globals.dart'; @@ -225,13 +226,35 @@ Future validateBitcode() async { fs.path.join(flutterFrameworkPath, 'Info.plist'), 'ClangVersion', ); - if (clangVersion != engineClangVersion) { - printStatus( + final Version engineClangSemVer = _parseVersionFromClang(engineClangVersion); + final Version clangSemVer = _parseVersionFromClang(clangVersion); + if (engineClangSemVer > clangSemVer) { + throwToolExit( 'The Flutter.framework at $flutterFrameworkPath was built ' 'with "${engineClangVersion ?? 'unknown'}", but the current version ' - 'of clang is "$clangVersion". This may result in failures when ' - 'archiving your application in Xcode.', - emphasis: true, + 'of clang is "$clangVersion". This will result in failures when trying to' + 'archive an IPA. To resolve this issue, update your version of Xcode to ' + 'at least $engineClangSemVer.', ); } } + +Version _parseVersionFromClang(String clangVersion) { + const String prefix = 'Apple LLVM version '; + void _invalid() { + throwToolExit('Unable to parse Clang version from "$clangVersion". ' + 'Expected a string like "$prefix #.#.# (clang-####.#.##.#)".'); + } + if (clangVersion == null || clangVersion.length <= prefix.length || !clangVersion.startsWith(prefix)) { + _invalid(); + } + final int lastSpace = clangVersion.lastIndexOf(' '); + if (lastSpace == -1) { + _invalid(); + } + final Version version = Version.parse(clangVersion.substring(prefix.length, lastSpace)); + if (version == null) { + _invalid(); + } + return version; +} diff --git a/packages/flutter_tools/test/general.shard/commands/build_aot_test.dart b/packages/flutter_tools/test/general.shard/commands/build_aot_test.dart index f697148d51..f55aa6feda 100644 --- a/packages/flutter_tools/test/general.shard/commands/build_aot_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/build_aot_test.dart @@ -82,18 +82,19 @@ void main() { const ['foo'], ); final RunResult clangResult = RunResult( - FakeProcessResult(stdout: 'BadVersion\nBlahBlah\n', stderr: ''), + FakeProcessResult(stdout: 'Apple LLVM version 10.0.0 (clang-4567.1.1.1)\nBlahBlah\n', stderr: ''), const ['foo'], ); when(mockXcode.otool(any)).thenAnswer((_) => Future.value(otoolResult)); when(mockXcode.clang(any)).thenAnswer((_) => Future.value(clangResult)); - when(mockIOSWorkflow.getPlistValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM Version 10.0.1'); + when(mockIOSWorkflow.getPlistValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM version 10.0.1 (clang-1234.1.12.1)'); - await validateBitcode(); - - expect( - bufferLogger.statusText, - startsWith('The Flutter.framework at ${flutterFramework.path} was built with "Apple LLVM Version 10.0.1'), + await expectToolExitLater( + validateBitcode(), + equals('The Flutter.framework at ios_profile/Flutter.framework was built with "Apple LLVM version 10.0.1 ' + '(clang-1234.1.12.1)", but the current version of clang is "Apple LLVM version 10.0.0 (clang-4567.1.1.1)". ' + 'This will result in failures when trying toarchive an IPA. To resolve this issue, update your version ' + 'of Xcode to at least 10.0.1.'), ); }, overrides: { Artifacts: () => LocalEngineArtifacts('/engine', 'ios_profile', 'host_profile'), @@ -104,7 +105,7 @@ void main() { IOSWorkflow: () => mockIOSWorkflow, }); - testUsingContext('build aot validates and succeeds', () async { + testUsingContext('build aot validates and succeeds - same version of Xcode', () async { final Directory flutterFramework = memoryFileSystem.directory('ios_profile/Flutter.framework') ..createSync(recursive: true); flutterFramework.childFile('Flutter').createSync(); @@ -115,12 +116,42 @@ void main() { const ['foo'], ); final RunResult clangResult = RunResult( - FakeProcessResult(stdout: 'Apple LLVM Version 10.0.1\nBlahBlah\n', stderr: ''), + FakeProcessResult(stdout: 'Apple LLVM version 10.0.1 (clang-1234.1.12.1)\nBlahBlah\n', stderr: ''), const ['foo'], ); when(mockXcode.otool(any)).thenAnswer((_) => Future.value(otoolResult)); when(mockXcode.clang(any)).thenAnswer((_) => Future.value(clangResult)); - when(mockIOSWorkflow.getPlistValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM Version 10.0.1'); + when(mockIOSWorkflow.getPlistValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM version 10.0.1 (clang-1234.1.12.1)'); + + await validateBitcode(); + + expect(bufferLogger.statusText, ''); + }, overrides: { + Artifacts: () => LocalEngineArtifacts('/engine', 'ios_profile', 'host_profile'), + FileSystem: () => memoryFileSystem, + ProcessManager: () => mockProcessManager, + Xcode: () => mockXcode, + Logger: () => bufferLogger, + IOSWorkflow: () => mockIOSWorkflow, + }); + + testUsingContext('build aot validates and succeeds when user has newer version of Xcode', () async { + final Directory flutterFramework = memoryFileSystem.directory('ios_profile/Flutter.framework') + ..createSync(recursive: true); + flutterFramework.childFile('Flutter').createSync(); + final File infoPlist = flutterFramework.childFile('Info.plist')..createSync(); + + final RunResult otoolResult = RunResult( + FakeProcessResult(stdout: '__LLVM', stderr: ''), + const ['foo'], + ); + final RunResult clangResult = RunResult( + FakeProcessResult(stdout: 'Apple LLVM version 11.0.1 (clang-1234.1.12.1)\nBlahBlah\n', stderr: ''), + const ['foo'], + ); + when(mockXcode.otool(any)).thenAnswer((_) => Future.value(otoolResult)); + when(mockXcode.clang(any)).thenAnswer((_) => Future.value(clangResult)); + when(mockIOSWorkflow.getPlistValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM version 10.0.1 (clang-1234.1.12.1)'); await validateBitcode();