From 357d02c87b75d74382db212812e85a5d7d93dd7f Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Tue, 15 Oct 2019 15:33:55 -0700 Subject: [PATCH] Always embed Flutter.framework build mode version from Xcode configuration (#42029) --- .gitignore | 1 + dev/bots/deploy_gallery.sh | 30 ++++++ .../splash_screen_kitchen_sink/.gitignore | 1 + .../splash_screen_load_rotate/.gitignore | 1 + .../splash_screen_trans_rotate/.gitignore | 1 + .../android_views/ios/.gitignore | 1 + .../android_views/ios/Podfile | 83 ++++++++++------- .../release_smoke_test/.gitignore | 1 + .../release_smoke_test/ios/Podfile | 83 ++++++++++------- examples/flutter_gallery/ios/Podfile | 84 ++++++++++++----- examples/platform_view/ios/Podfile | 91 +++++++++++-------- examples/platform_view/ios/Podfile.lock | 17 +--- packages/flutter_tools/bin/xcode_backend.sh | 1 + .../lib/src/macos/cocoapods.dart | 41 ++++++++- packages/flutter_tools/lib/src/project.dart | 9 ++ .../templates/app/ios.tmpl/.gitignore | 1 + .../templates/cocoapods/Podfile-ios-objc | 83 ++++++++++------- .../templates/cocoapods/Podfile-ios-swift | 83 ++++++++++------- .../templates/package/.gitignore.tmpl | 1 + .../general.shard/macos/cocoapods_test.dart | 21 +++++ 20 files changed, 424 insertions(+), 210 deletions(-) diff --git a/.gitignore b/.gitignore index cc3ca2d398..4946cd91d8 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,7 @@ unlinked_spec.ds **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip diff --git a/dev/bots/deploy_gallery.sh b/dev/bots/deploy_gallery.sh index 24e7dfa951..f372fec181 100755 --- a/dev/bots/deploy_gallery.sh +++ b/dev/bots/deploy_gallery.sh @@ -54,6 +54,36 @@ if [[ "$SHARD" = "deploy_gallery" ]]; then ( cd examples/flutter_gallery flutter build ios --release --no-codesign -t lib/main_publish.dart + + # flutter build ios will run CocoaPods script. Check generated locations. + if [[ ! -d "ios/Pods" ]]; then + echo "Error: pod install failed to setup plugins" + exit 1 + fi + + if [[ ! -d "ios/.symlinks/plugins" ]]; then + echo "Error: pod install failed to setup plugin symlinks" + exit 1 + fi + + if [[ -d "ios/.symlinks/flutter" ]]; then + echo "Error: pod install created flutter symlink" + exit 1 + fi + + if [[ ! -d "build/ios/iphoneos/Runner.app/Frameworks/App.framework/flutter_assets" ]]; then + echo "Error: flutter_assets not assembled" + exit 1 + fi + + if [[ + -d "build/ios/iphoneos/Runner.app/Frameworks/App.framework/flutter_assets/isolate_snapshot_data" || + -d "build/ios/iphoneos/Runner.app/Frameworks/App.framework/flutter_assets/kernel_blob.bin" || + -d "build/ios/iphoneos/Runner.app/Frameworks/App.framework/flutter_assets/vm_snapshot_data" + ]]; then + echo "Error: compiled debug version of app with --release flag" + exit 1 + fi ) echo "iOS Flutter Gallery built" if [[ -z "$CIRRUS_PR" ]]; then diff --git a/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/.gitignore b/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/.gitignore index 6be3a02e49..05fc69bfc5 100644 --- a/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/.gitignore +++ b/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/.gitignore @@ -60,6 +60,7 @@ **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip diff --git a/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/.gitignore b/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/.gitignore index 2ddde2a5e3..716b5a9621 100644 --- a/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/.gitignore +++ b/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/.gitignore @@ -57,6 +57,7 @@ **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip diff --git a/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/.gitignore b/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/.gitignore index 2ddde2a5e3..716b5a9621 100644 --- a/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/.gitignore +++ b/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/.gitignore @@ -57,6 +57,7 @@ **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip diff --git a/dev/integration_tests/android_views/ios/.gitignore b/dev/integration_tests/android_views/ios/.gitignore index 82bd1e49eb..641f3c9d95 100644 --- a/dev/integration_tests/android_views/ios/.gitignore +++ b/dev/integration_tests/android_views/ios/.gitignore @@ -38,6 +38,7 @@ Icon? /Flutter/flutter_assets/ /Flutter/App.framework /Flutter/Flutter.framework +/Flutter/Flutter.podspec /Flutter/Generated.xcconfig /Flutter/flutter_export_environment.sh /ServiceDefinitions.json diff --git a/dev/integration_tests/android_views/ios/Podfile b/dev/integration_tests/android_views/ios/Podfile index 8d3767dad0..71404916be 100644 --- a/dev/integration_tests/android_views/ios/Podfile +++ b/dev/integration_tests/android_views/ios/Podfile @@ -15,51 +15,66 @@ def parse_KV_file(file, separator='=') if !File.exists? file_abs_path return []; end - pods_ary = [] + generated_key_values = {} skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) { |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - pods_ary.push({:name => podname, :path => podpath}); - else - puts "Invalid plugin specification: #{line}" - end - } - return pods_ary + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values end target 'Runner' do use_modular_headers! + + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock # referring to absolute paths on developers' machines. system('rm -rf .symlinks') system('mkdir -p .symlinks/plugins') - - # Flutter Pods - generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') - if generated_xcode_build_settings.empty? - puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." - end - generated_xcode_build_settings.map { |p| - if p[:name] == 'FLUTTER_FRAMEWORK_DIR' - symlink = File.join('.symlinks', 'flutter') - File.symlink(File.dirname(p[:path]), symlink) - pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) - end - } - - # Plugin Pods plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.map { |p| - symlink = File.join('.symlinks', 'plugins', p[:name]) - File.symlink(p[:path], symlink) - pod p[:name], :path => File.join(symlink, 'ios') - } + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end end # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. diff --git a/dev/integration_tests/release_smoke_test/.gitignore b/dev/integration_tests/release_smoke_test/.gitignore index 2ddde2a5e3..716b5a9621 100644 --- a/dev/integration_tests/release_smoke_test/.gitignore +++ b/dev/integration_tests/release_smoke_test/.gitignore @@ -57,6 +57,7 @@ **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip diff --git a/dev/integration_tests/release_smoke_test/ios/Podfile b/dev/integration_tests/release_smoke_test/ios/Podfile index 89b9a88d70..71404916be 100644 --- a/dev/integration_tests/release_smoke_test/ios/Podfile +++ b/dev/integration_tests/release_smoke_test/ios/Podfile @@ -15,51 +15,66 @@ def parse_KV_file(file, separator='=') if !File.exists? file_abs_path return []; end - pods_ary = [] + generated_key_values = {} skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) { |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - pods_ary.push({:name => podname, :path => podpath}); - else - puts "Invalid plugin specification: #{line}" - end - } - return pods_ary + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values end target 'Runner' do use_modular_headers! + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock # referring to absolute paths on developers' machines. system('rm -rf .symlinks') system('mkdir -p .symlinks/plugins') - - # Flutter Pods - generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') - if generated_xcode_build_settings.empty? - puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." - end - generated_xcode_build_settings.map { |p| - if p[:name] == 'FLUTTER_FRAMEWORK_DIR' - symlink = File.join('.symlinks', 'flutter') - File.symlink(File.dirname(p[:path]), symlink) - pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) - end - } - - # Plugin Pods plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.map { |p| - symlink = File.join('.symlinks', 'plugins', p[:name]) - File.symlink(p[:path], symlink) - pod p[:name], :path => File.join(symlink, 'ios') - } + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end end # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. diff --git a/examples/flutter_gallery/ios/Podfile b/examples/flutter_gallery/ios/Podfile index 61291f15f2..41b791fcbf 100644 --- a/examples/flutter_gallery/ios/Podfile +++ b/examples/flutter_gallery/ios/Podfile @@ -4,39 +4,77 @@ # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' -def parse_KV_file(file,separator='=') +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') file_abs_path = File.expand_path(file) if !File.exists? file_abs_path return []; end - pods_ary = [] + generated_key_values = {} skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) { |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - pods_ary.push({:name => podname,:path=>podpath}); - else - puts "Invalid plugin specification: #{line}" - end - } - return pods_ary + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values end target 'Runner' do use_modular_headers! - # Flutter Pods - pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR'] - + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + # Plugin Pods - plugin_pods = parse_KV_file("../.flutter-plugins") - plugin_pods.map{ |p| - pod p[:name], :path => File.expand_path("ios",p[:path]) - } + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end end # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. @@ -49,4 +87,4 @@ post_install do |installer| config.build_settings['ENABLE_BITCODE'] = 'NO' end end -end \ No newline at end of file +end diff --git a/examples/platform_view/ios/Podfile b/examples/platform_view/ios/Podfile index 8c87860110..71404916be 100644 --- a/examples/platform_view/ios/Podfile +++ b/examples/platform_view/ios/Podfile @@ -4,58 +4,77 @@ # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + def parse_KV_file(file, separator='=') file_abs_path = File.expand_path(file) if !File.exists? file_abs_path return []; end - pods_ary = [] + generated_key_values = {} skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) { |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - pods_ary.push({:name => podname, :path => podpath}); - else - puts "Invalid plugin specification: #{line}" - end - } - return pods_ary + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values end target 'Runner' do use_modular_headers! + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock # referring to absolute paths on developers' machines. system('rm -rf .symlinks') system('mkdir -p .symlinks/plugins') - - # Flutter Pods - generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') - if generated_xcode_build_settings.empty? - puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." - end - generated_xcode_build_settings.map { |p| - if p[:name] == 'FLUTTER_FRAMEWORK_DIR' - symlink = File.join('.symlinks', 'flutter') - File.symlink(File.dirname(p[:path]), symlink) - pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) - end - } - - # Plugin Pods plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.map { |p| - symlink = File.join('.symlinks', 'plugins', p[:name]) - File.symlink(p[:path], symlink) - pod p[:name], :path => File.join(symlink, 'ios') - } - - pod 'MaterialControls' + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end end # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. diff --git a/examples/platform_view/ios/Podfile.lock b/examples/platform_view/ios/Podfile.lock index b8ac2f4207..3c4f716588 100644 --- a/examples/platform_view/ios/Podfile.lock +++ b/examples/platform_view/ios/Podfile.lock @@ -1,23 +1,16 @@ PODS: - Flutter (1.0.0) - - MaterialControls (1.2.2) DEPENDENCIES: - - Flutter (from `.symlinks/flutter/ios`) - - MaterialControls - -SPEC REPOS: - https://github.com/cocoapods/specs.git: - - MaterialControls + - Flutter (from `Flutter`) EXTERNAL SOURCES: Flutter: - :path: ".symlinks/flutter/ios" + :path: Flutter SPEC CHECKSUMS: - Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a - MaterialControls: 1c6b29e78d3a13d8dd6a67ed31b6d26eb5de8f72 + Flutter: 0e3d915762c693b495b44d77113d4970485de6ec -PODFILE CHECKSUM: 80af51c01ee3f0969ddbf2d1f0dcd6f44fad2c52 +PODFILE CHECKSUM: 3dbe063e9c90a5d7c9e4e76e70a821b9e2c1d271 -COCOAPODS: 1.7.1 +COCOAPODS: 1.8.3 diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh index a28dfb5dcf..a80ce3126b 100755 --- a/packages/flutter_tools/bin/xcode_backend.sh +++ b/packages/flutter_tools/bin/xcode_backend.sh @@ -146,6 +146,7 @@ BuildApp() { RunCommand find "${derived_dir}/engine/Flutter.framework" -type f \( -name '*.h' -o -name '*.modulemap' -o -name '*.plist' \) -exec chmod a-w "{}" \; else RunCommand rm -rf -- "${derived_dir}/Flutter.framework" + RunCommand cp -- "${flutter_podspec}" "${derived_dir}" RunCommand cp -r -- "${flutter_framework}" "${derived_dir}" # Make headers, plists, and modulemap files read-only to discourage editing. RunCommand find "${derived_dir}/Flutter.framework" -type f \( -name '*.h' -o -name '*.modulemap' -o -name '*.plist' \) -exec chmod a-w "{}" \; diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart index 8cfac2a2c4..9a7a64fd93 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapods.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:file/file.dart'; import 'package:meta/meta.dart'; import '../base/common.dart'; @@ -34,12 +35,19 @@ const String brokenCocoaPodsConsequence = ''' This can happen if the version of Ruby that CocoaPods was installed with is different from the one being used to invoke it. This can usually be fixed by re-installing CocoaPods. For more info, see https://github.com/flutter/flutter/issues/14293.'''; +const String outOfDatePodfileConsequence = ''' + This can cause a mismatched version of Flutter to be embedded in your app, which may result in App Store submission rejection or crashes. + If you have local Podfile edits you would like to keep, see https://github.com/flutter/flutter/issues/24641 for instructions.'''; + const String cocoaPodsInstallInstructions = ''' sudo gem install cocoapods'''; const String cocoaPodsUpgradeInstructions = ''' sudo gem install cocoapods'''; +const String podfileMigrationInstructions = ''' + rm ios/Podfile'''; + CocoaPods get cocoaPods => context.get(); /// Result of evaluating the CocoaPods installation. @@ -131,13 +139,15 @@ class CocoaPods { if (!xcodeProject.podfile.existsSync()) { throwToolExit('Podfile missing'); } + bool podsProcessed = false; if (await _checkPodCondition()) { if (_shouldRunPodInstall(xcodeProject, dependenciesChanged)) { await _runPodInstall(xcodeProject, engineDir); - return true; + podsProcessed = true; } + _warnIfPodfileOutOfDate(xcodeProject); } - return false; + return podsProcessed; } /// Make sure the CocoaPods tools are in the right states. @@ -291,7 +301,6 @@ class CocoaPods { ['pod', 'install', '--verbose'], workingDirectory: fs.path.dirname(xcodeProject.podfile.path), environment: { - // For backward compatibility with previously created Podfile only. 'FLUTTER_FRAMEWORK_DIR': engineDirectory, // See https://github.com/flutter/flutter/issues/10873. // CocoaPods analytics adds a lot of latency. @@ -326,4 +335,30 @@ class CocoaPods { ); } } + + // Previously, the Podfile created a symlink to the cached artifacts engine framework + // and installed the Flutter pod from that path. This could get out of sync with the copy + // of the Flutter engine that was copied to ios/Flutter by the xcode_backend script. + // It was possible for the symlink to point to a Debug version of the engine when the + // Xcode build configuration was Release, which caused App Store submission rejections. + // + // Warn the user if they are still symlinking to the framework. + void _warnIfPodfileOutOfDate(XcodeBasedProject xcodeProject) { + if (xcodeProject is! IosProject) { + return; + } + final Link flutterSymlink = fs.link(fs.path.join( + xcodeProject.symlinks.path, + 'flutter', + )); + if (flutterSymlink.existsSync()) { + printError( + 'Warning: Podfile is out of date\n' + '$outOfDatePodfileConsequence\n' + 'To regenerate the Podfile, run:\n' + '$podfileMigrationInstructions\n', + emphasis: true, + ); + } + } } diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 3b7ad27b17..a582d8df1d 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -288,6 +288,9 @@ abstract class XcodeBasedProject { /// True if the host app project is using Swift. Future get isSwift; + + /// Directory containing symlinks to pub cache plugins source generated on `pod install`. + Directory get symlinks; } /// Represents the iOS sub-project of a Flutter project. @@ -350,6 +353,9 @@ class IosProject implements XcodeBasedProject { /// The 'Info.plist' file of the host app. File get hostInfoPlist => hostAppRoot.childDirectory(_hostAppBundleName).childFile('Info.plist'); + @override + Directory get symlinks => _flutterLibRoot.childDirectory('.symlinks'); + @override Directory get xcodeProject => hostAppRoot.childDirectory('$_hostAppBundleName.xcodeproj'); @@ -742,6 +748,9 @@ class MacOSProject implements XcodeBasedProject { @override Directory get xcodeWorkspace => _macOSDirectory.childDirectory('$_hostAppBundleName.xcworkspace'); + @override + Directory get symlinks => ephemeralDirectory.childDirectory('.symlinks'); + @override Future get isSwift async => true; diff --git a/packages/flutter_tools/templates/app/ios.tmpl/.gitignore b/packages/flutter_tools/templates/app/ios.tmpl/.gitignore index f78c1480b6..e96ef602b8 100644 --- a/packages/flutter_tools/templates/app/ios.tmpl/.gitignore +++ b/packages/flutter_tools/templates/app/ios.tmpl/.gitignore @@ -16,6 +16,7 @@ xcuserdata **/.generated/ Flutter/App.framework Flutter/Flutter.framework +Flutter/Flutter.podspec Flutter/Generated.xcconfig Flutter/app.flx Flutter/app.zip diff --git a/packages/flutter_tools/templates/cocoapods/Podfile-ios-objc b/packages/flutter_tools/templates/cocoapods/Podfile-ios-objc index 89b9a88d70..71404916be 100644 --- a/packages/flutter_tools/templates/cocoapods/Podfile-ios-objc +++ b/packages/flutter_tools/templates/cocoapods/Podfile-ios-objc @@ -15,51 +15,66 @@ def parse_KV_file(file, separator='=') if !File.exists? file_abs_path return []; end - pods_ary = [] + generated_key_values = {} skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) { |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - pods_ary.push({:name => podname, :path => podpath}); - else - puts "Invalid plugin specification: #{line}" - end - } - return pods_ary + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values end target 'Runner' do use_modular_headers! + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock # referring to absolute paths on developers' machines. system('rm -rf .symlinks') system('mkdir -p .symlinks/plugins') - - # Flutter Pods - generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') - if generated_xcode_build_settings.empty? - puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." - end - generated_xcode_build_settings.map { |p| - if p[:name] == 'FLUTTER_FRAMEWORK_DIR' - symlink = File.join('.symlinks', 'flutter') - File.symlink(File.dirname(p[:path]), symlink) - pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) - end - } - - # Plugin Pods plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.map { |p| - symlink = File.join('.symlinks', 'plugins', p[:name]) - File.symlink(p[:path], symlink) - pod p[:name], :path => File.join(symlink, 'ios') - } + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end end # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. diff --git a/packages/flutter_tools/templates/cocoapods/Podfile-ios-swift b/packages/flutter_tools/templates/cocoapods/Podfile-ios-swift index 1f7a48426f..b30a428b5e 100644 --- a/packages/flutter_tools/templates/cocoapods/Podfile-ios-swift +++ b/packages/flutter_tools/templates/cocoapods/Podfile-ios-swift @@ -15,52 +15,67 @@ def parse_KV_file(file, separator='=') if !File.exists? file_abs_path return []; end - pods_ary = [] + generated_key_values = {} skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) { |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - pods_ary.push({:name => podname, :path => podpath}); - else - puts "Invalid plugin specification: #{line}" - end - } - return pods_ary + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values end target 'Runner' do use_frameworks! use_modular_headers! + + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock # referring to absolute paths on developers' machines. system('rm -rf .symlinks') system('mkdir -p .symlinks/plugins') - - # Flutter Pods - generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') - if generated_xcode_build_settings.empty? - puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." - end - generated_xcode_build_settings.map { |p| - if p[:name] == 'FLUTTER_FRAMEWORK_DIR' - symlink = File.join('.symlinks', 'flutter') - File.symlink(File.dirname(p[:path]), symlink) - pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) - end - } - - # Plugin Pods plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.map { |p| - symlink = File.join('.symlinks', 'plugins', p[:name]) - File.symlink(p[:path], symlink) - pod p[:name], :path => File.join(symlink, 'ios') - } + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end end # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. diff --git a/packages/flutter_tools/templates/package/.gitignore.tmpl b/packages/flutter_tools/templates/package/.gitignore.tmpl index 3132dc5ff7..6ffedaea4f 100644 --- a/packages/flutter_tools/templates/package/.gitignore.tmpl +++ b/packages/flutter_tools/templates/package/.gitignore.tmpl @@ -57,6 +57,7 @@ build/ **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip 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 5d058e7134..b81afb1a86 100644 --- a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart @@ -314,6 +314,27 @@ void main() { ProcessManager: () => mockProcessManager, }); + testUsingContext('prints warning, if Podfile is out of date', () async { + pretendPodIsInstalled(); + + fs.file(fs.path.join('project', 'ios', 'Podfile')) + ..createSync() + ..writeAsStringSync('Existing Podfile'); + + final Directory symlinks = projectUnderTest.ios.symlinks + ..createSync(recursive: true); + symlinks.childLink('flutter').createSync('cache'); + + await cocoaPodsUnderTest.processPods( + xcodeProject: projectUnderTest.ios, + engineDir: 'engine/path', + ); + expect(testLogger.errorText, contains('Warning: Podfile is out of date')); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => mockProcessManager, + }); + testUsingContext('throws, if Podfile is missing.', () async { pretendPodIsInstalled(); try {