diff --git a/dev/devicelab/bin/tasks/plugin_test_ios.dart b/dev/devicelab/bin/tasks/plugin_test_ios.dart index 4014fa18bf..c1b66bfe9e 100644 --- a/dev/devicelab/bin/tasks/plugin_test_ios.dart +++ b/dev/devicelab/bin/tasks/plugin_test_ios.dart @@ -9,6 +9,8 @@ Future main() async { await task(combine([ PluginTest('ios', ['-i', 'objc', '--platforms=ios']).call, PluginTest('ios', ['-i', 'swift', '--platforms=ios']).call, + // Test that app builds with Flutter as a transitive dependency. + PluginTest('ios', ['-i', 'objc', '--platforms=ios'], cocoapodsTransitiveFlutterDependency: true).call, // Test that Dart-only plugins are supported. PluginTest('ios', ['--platforms=ios'], dartOnlyPlugin: true).call, // Test that shared darwin directories are supported. diff --git a/dev/devicelab/lib/tasks/plugin_tests.dart b/dev/devicelab/lib/tasks/plugin_tests.dart index 51719d05ed..2f9943c57e 100644 --- a/dev/devicelab/lib/tasks/plugin_tests.dart +++ b/dev/devicelab/lib/tasks/plugin_tests.dart @@ -35,6 +35,7 @@ class PluginTest { this.dartOnlyPlugin = false, this.sharedDarwinSource = false, this.template = 'plugin', + this.cocoapodsTransitiveFlutterDependency = false, }); final String buildTarget; @@ -44,6 +45,7 @@ class PluginTest { final bool dartOnlyPlugin; final bool sharedDarwinSource; final String template; + final bool cocoapodsTransitiveFlutterDependency; Future call() async { final Directory tempDir = @@ -80,6 +82,11 @@ class PluginTest { await app.addPlugin('path_provider'); section('Build app'); await app.build(buildTarget, validateNativeBuildProject: !dartOnlyPlugin); + if (cocoapodsTransitiveFlutterDependency) { + section('Test app with Flutter as a transitive CocoaPods dependency'); + await app.addCocoapodsTransitiveFlutterDependency(); + await app.build(buildTarget, validateNativeBuildProject: !dartOnlyPlugin); + } if (runFlutterTest) { section('Test app'); await app.runFlutterTest(); @@ -361,6 +368,57 @@ public class $pluginClass: NSObject, FlutterPlugin { return project; } + /// Creates a Pod that uses a Flutter plugin as a dependency and therefore + /// Flutter as a transitive dependency. + Future addCocoapodsTransitiveFlutterDependency() async { + final String iosDirectoryPath = path.join(rootPath, 'ios'); + + final File nativePod = File(path.join( + iosDirectoryPath, + 'NativePod', + 'NativePod.podspec', + )); + nativePod.createSync(recursive: true); + nativePod.writeAsStringSync(''' +Pod::Spec.new do |s| + s.name = 'NativePod' + s.version = '1.0.0' + s.summary = 'A pod to test Flutter as a transitive dependency.' + s.homepage = 'https://flutter.dev' + s.license = { :type => 'BSD' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :path => '.' } + s.source_files = "Classes", "Classes/**/*.{h,m}" + s.dependency 'plugintest' +end +'''); + + final File nativePodClass = File(path.join( + iosDirectoryPath, + 'NativePod', + 'Classes', + 'NativePodTest.m', + )); + nativePodClass.createSync(recursive: true); + nativePodClass.writeAsStringSync(''' +#import + +@interface NativePodTest : NSObject + +@end + +@implementation NativePodTest + +@end +'''); + + final File podfileFile = File(path.join(iosDirectoryPath, 'Podfile')); + final List podfileContents = podfileFile.readAsLinesSync(); + final int index = podfileContents.indexWhere((String line) => line.contains('flutter_install_all_ios_pods')); + podfileContents.insert(index, "pod 'NativePod', :path => 'NativePod'"); + podfileFile.writeAsStringSync(podfileContents.join('\n')); + } + // Make the platform version artificially low to test that the "deployment // version too low" warning is never emitted. void _reduceDarwinPluginMinimumVersion(String plugin, String target) { diff --git a/packages/flutter_tools/bin/podhelper.rb b/packages/flutter_tools/bin/podhelper.rb index 2ec1e82e42..438c4baf7f 100644 --- a/packages/flutter_tools/bin/podhelper.rb +++ b/packages/flutter_tools/bin/podhelper.rb @@ -19,6 +19,20 @@ def flutter_ios_podfile_setup; end # Same as flutter_ios_podfile_setup for macOS. def flutter_macos_podfile_setup; end +# Determine whether the target depends on Flutter (including transitive dependency) +def depends_on_flutter(target, engine_pod_name) + target.dependencies.any? do |dependency| + if dependency.name == engine_pod_name + return true + end + + if depends_on_flutter(dependency.target, engine_pod_name) + return true + end + end + return false +end + # Add iOS build settings to pod targets. # # @example @@ -68,8 +82,8 @@ def flutter_additional_ios_build_settings(target) # ARC code targeting iOS 8 does not build on Xcode 14.3. Force to at least iOS 9. build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0' if force_to_arc_supported_min - # Skip other updates if it's not a Flutter plugin (transitive dependency). - next unless target.dependencies.any? { |dependency| dependency.name == 'Flutter' } + # Skip other updates if it does not depend on Flutter (including transitive dependency) + next unless depends_on_flutter(target, 'Flutter') # Bitcode is deprecated, Flutter.framework bitcode blob will have been stripped. build_configuration.build_settings['ENABLE_BITCODE'] = 'NO' @@ -139,8 +153,8 @@ def flutter_additional_macos_build_settings(target) # ARC code targeting macOS 10.10 does not build on Xcode 14.3. Force to at least macOS 10.11. build_configuration.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '10.11' if force_to_arc_supported_min - # Skip other updates if it's not a Flutter plugin (transitive dependency). - next unless target.dependencies.any? { |dependency| dependency.name == 'FlutterMacOS' } + # Skip other updates if it does not depend on Flutter (including transitive dependency) + next unless depends_on_flutter(target, 'FlutterMacOS') # Profile can't be derived from the CocoaPods build configuration. Use release framework (for linking only). configuration_engine_dir = local_engine || (build_configuration.type == :debug ? debug_framework_dir : release_framework_dir)