diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index 671ffd5c1f..6f42bd5758 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -135,6 +135,7 @@ Future runInContext( platform: globals.platform, xcodeProjectInterpreter: globals.xcodeProjectInterpreter, artifacts: globals.artifacts, + usage: globals.flutterUsage, ), CocoaPodsValidator: () => CocoaPodsValidator( globals.cocoaPods, diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart index e8c12fef3e..f8d9870953 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapods.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart @@ -12,6 +12,7 @@ import '../base/error_handling_io.dart'; import '../base/file_system.dart'; import '../base/io.dart'; import '../base/logger.dart'; +import '../base/os.dart'; import '../base/platform.dart'; import '../base/process.dart'; import '../base/version.dart'; @@ -19,6 +20,7 @@ import '../build_info.dart'; import '../cache.dart'; import '../ios/xcodeproj.dart'; import '../project.dart'; +import '../reporting/reporting.dart'; const String noCocoaPodsConsequence = ''' CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart side. @@ -82,23 +84,33 @@ class CocoaPods { @required Logger logger, @required Platform platform, @required Artifacts artifacts, + @required Usage usage, }) : _fileSystem = fileSystem, _processManager = processManager, _xcodeProjectInterpreter = xcodeProjectInterpreter, _logger = logger, _platform = platform, _artifacts = artifacts, + _usage = usage, _processUtils = ProcessUtils(processManager: processManager, logger: logger), - _fileSystemUtils = FileSystemUtils(fileSystem: fileSystem, platform: platform); + _fileSystemUtils = FileSystemUtils(fileSystem: fileSystem, platform: platform), + _operatingSystemUtils = OperatingSystemUtils( + fileSystem: fileSystem, + logger: logger, + platform: platform, + processManager: processManager, + ); final FileSystem _fileSystem; final ProcessManager _processManager; final FileSystemUtils _fileSystemUtils; final ProcessUtils _processUtils; + final OperatingSystemUtils _operatingSystemUtils; final XcodeProjectInterpreter _xcodeProjectInterpreter; final Logger _logger; final Platform _platform; final Artifacts _artifacts; + final Usage _usage; Future _versionText; @@ -370,14 +382,31 @@ class CocoaPods { } void _diagnosePodInstallFailure(ProcessResult result) { - final dynamic stdout = result.stdout; - if (stdout is String && stdout.contains('out-of-date source repos')) { + if (result.stdout is! String) { + return; + } + final String stdout = result.stdout as String; + if (stdout.contains('out-of-date source repos')) { _logger.printError( "Error: CocoaPods's specs repository is too out-of-date to satisfy dependencies.\n" 'To update the CocoaPods specs, run:\n' ' pod repo update\n', emphasis: true, ); + } else if (stdout.contains('Init_ffi_c') && + stdout.contains('symbol not found') && + _operatingSystemUtils.hostPlatform == HostPlatform.darwin_arm) { + // https://github.com/flutter/flutter/issues/70796 + UsageEvent( + 'pod-install-failure', + 'arm-ffi', + flutterUsage: _usage, + ).send(); + _logger.printError( + 'Error: To set up CocoaPods for ARM macOS, run:\n' + ' arch -x86_64 sudo gem install ffi\n', + emphasis: true, + ); } } diff --git a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart index fdeeabdc54..9532591649 100644 --- a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart @@ -16,6 +16,7 @@ import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/macos/cocoapods.dart'; import 'package:flutter_tools/src/plugins.dart'; import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:mockito/mockito.dart'; import 'package:process/process.dart'; @@ -32,6 +33,7 @@ void main() { CocoaPods cocoaPodsUnderTest; InvokeProcess resultOfPodVersion; BufferLogger logger; + Usage usage; void pretendPodVersionFails() { resultOfPodVersion = () async => exitsWithError(); @@ -68,15 +70,17 @@ void main() { projectUnderTest = FlutterProject.fromDirectory(fileSystem.directory('project')); projectUnderTest.ios.xcodeProject.createSync(recursive: true); projectUnderTest.macos.xcodeProject.createSync(recursive: true); + usage = Usage.test(); cocoaPodsUnderTest = CocoaPods( fileSystem: fileSystem, processManager: mockProcessManager, logger: logger, - platform: FakePlatform(), + platform: FakePlatform(operatingSystem: 'macos'), artifacts: Artifacts.test(), xcodeProjectInterpreter: mockXcodeProjectInterpreter, + usage: usage, ); - pretendPodVersionIs('1.8.0'); + pretendPodVersionIs('1.9.0'); fileSystem.file(fileSystem.path.join( Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-ios-objc', )) @@ -450,6 +454,79 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by } }); + testWithoutContext('ffi failure on ARM macOS prompts gem install', () async { + pretendPodIsInstalled(); + fileSystem.file(fileSystem.path.join('project', 'ios', 'Podfile')) + ..createSync() + ..writeAsStringSync('Existing Podfile'); + + when(mockProcessManager.runSync(['sysctl', 'hw.optional.arm64'])) + .thenReturn(ProcessResult(0, 0, 'hw.optional.arm64: 1', '')); + + when(mockProcessManager.run( + ['pod', 'install', '--verbose'], + workingDirectory: 'project/ios', + environment: { + 'COCOAPODS_DISABLE_STATS': 'true', + 'LANG': 'en_US.UTF-8', + }, + )).thenAnswer((_) async => exitsWithError( + 'LoadError - dlsym(0x7fbbeb6837d0, Init_ffi_c): symbol not found - /Library/Ruby/Gems/2.6.0/gems/ffi-1.13.1/lib/ffi_c.bundle', + )); + + // Capture Usage.test() events. + final StringBuffer buffer = + await capturedConsolePrint(() => expectToolExitLater( + cocoaPodsUnderTest.processPods( + xcodeProject: projectUnderTest.ios, + buildMode: BuildMode.debug, + ), + equals('Error running pod install'), + )); + expect( + logger.errorText, + contains('set up CocoaPods for ARM macOS'), + ); + expect(buffer.toString(), + contains('event {category: pod-install-failure, action: arm-ffi')); + }); + + testWithoutContext('ffi failure on x86 macOS does not prompt gem install', () async { + pretendPodIsInstalled(); + fileSystem.file(fileSystem.path.join('project', 'ios', 'Podfile')) + ..createSync() + ..writeAsStringSync('Existing Podfile'); + + when(mockProcessManager.runSync(['sysctl', 'hw.optional.arm64'])) + .thenReturn(ProcessResult(0, 1, '', '')); + + when(mockProcessManager.run( + ['pod', 'install', '--verbose'], + workingDirectory: 'project/ios', + environment: { + 'COCOAPODS_DISABLE_STATS': 'true', + 'LANG': 'en_US.UTF-8', + }, + )).thenAnswer((_) async => exitsWithError( + 'LoadError - dlsym(0x7fbbeb6837d0, Init_ffi_c): symbol not found - /Library/Ruby/Gems/2.6.0/gems/ffi-1.13.1/lib/ffi_c.bundle', + )); + + // Capture Usage.test() events. + final StringBuffer buffer = + await capturedConsolePrint(() => expectToolExitLater( + cocoaPodsUnderTest.processPods( + xcodeProject: projectUnderTest.ios, + buildMode: BuildMode.debug, + ), + equals('Error running pod install'), + )); + expect( + logger.errorText, + isNot(contains('ARM macOS')), + ); + expect(buffer.isEmpty, true); + }); + testWithoutContext('run pod install, if Podfile.lock is missing', () async { pretendPodIsInstalled(); projectUnderTest.ios.podfile