# Overrides CocoaPods `Installer`/'Podfile' classes to patch podspecs on the fly # See: https://github.com/CocoaPods/CocoaPods/blob/master/lib/cocoapods/installer.rb # See: https://github.com/CocoaPods/Core/blob/master/lib/cocoapods-core/podfile.rb#L160 # # This is necessary to disable `USE_FRAMEWORKS` for specific pods that include # React Native Core headers in their public headers, which causes issues when # building them as dynamic frameworks with modular headers enabled. module Pod class Podfile public def framework_modules_to_patch @framework_modules_to_patch ||= ['ExpoModulesCore', 'Expo', 'ReactAppDependencyProvider', 'expo-dev-menu'] end def expo_add_modules_to_patch(modules) framework_modules_to_patch.concat(modules) end end class Installer private _original_perform_post_install_actions = instance_method(:perform_post_install_actions) _original_run_podfile_pre_install_hooks = instance_method(:run_podfile_pre_install_hooks) _script_phase_name = '[Expo Autolinking] Run Codegen with autolinking' public define_method(:perform_post_install_actions) do # Call original implementation first _original_perform_post_install_actions.bind(self).() # Next we'll perform an Expo workaround for Codegen in React Native where it uses the wrong output path for # the generated files. This can be remove when the following PR is merged and released upstream: # https://github.com/facebook/react-native/pull/54066 # TODO: chrfalch - remove when RN PR is released # Find the ReactCodegen pod target in the pods project react_codegen_native_target = self.pods_project.targets.find { |target| target.name == 'ReactCodegen' } if react_codegen_native_target # Check if the build phase already exists already_exists = react_codegen_native_target.build_phases.any? do |phase| phase.is_a?(Xcodeproj::Project::Object::PBXShellScriptBuildPhase) && phase.name == _script_phase_name end if !already_exists Pod::UI.puts "[Expo] ".blue + "Adding '#{_script_phase_name}' build phase to ReactCodegen" # Create the new shell script build phase phase = react_codegen_native_target.new_shell_script_build_phase(_script_phase_name) phase.shell_path = '/bin/sh' phase.shell_script = <<~SH # Remove this step when the fix is merged and released. # See: https://github.com/facebook/react-native/pull/54066 # This re-runs Codegen without the broken "scripts/react_native_pods_utils/script_phases.sh" script, causing Codegen to run without autolinking. # Instead of using "script_phases.sh" which always runs inside DerivedData, we run it in the normal "/ios" folder # See: https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods_utils/script_phases.sh pushd "$PODS_ROOT/../" > /dev/null RCT_SCRIPT_POD_INSTALLATION_ROOT="$PODS_ROOT/.." popd >/dev/null export RCT_SCRIPT_RN_DIR="$REACT_NATIVE_PATH" # This is set by Expo export RCT_SCRIPT_APP_PATH="$RCT_SCRIPT_POD_INSTALLATION_ROOT/.." export RCT_SCRIPT_OUTPUT_DIR="$RCT_SCRIPT_POD_INSTALLATION_ROOT" export RCT_SCRIPT_TYPE="withCodegenDiscovery" # This is the broken script that runs inside DerivedData, meaning it can't find the autolinking result in `ios/build/generated/autolinking.json`. # Resulting in Codegen running with it's own autolinking, not discovering transitive peer dependencies. # export SCRIPT_PHASES_SCRIPT="$RCT_SCRIPT_RN_DIR/scripts/react_native_pods_utils/script_phases.sh" export WITH_ENVIRONMENT="$RCT_SCRIPT_RN_DIR/scripts/xcode/with-environment.sh" # Start of workaround code # Load the $NODE_BINARY from the "with-environment.sh" script source "$WITH_ENVIRONMENT" # Run this script directly in the right folders: # https://github.com/facebook/react-native/blob/3f7f9d8fb8beb41408d092870a7c7cac58029a4d/packages/react-native/scripts/react_native_pods_utils/script_phases.sh#L96-L101 pushd "$RCT_SCRIPT_RN_DIR" >/dev/null || exit 1 set -x "$NODE_BINARY" "scripts/generate-codegen-artifacts.js" --path "$RCT_SCRIPT_APP_PATH" --outputPath "$RCT_SCRIPT_OUTPUT_DIR" --targetPlatform "ios" set +x popd >/dev/null || exit 1 # End of workaround code SH # Find the index of the "Compile Sources" phase (PBXSourcesBuildPhase) compile_sources_index = react_codegen_native_target.build_phases.find_index do |p| p.is_a?(Xcodeproj::Project::Object::PBXSourcesBuildPhase) end if compile_sources_index # Remove the phase from its current position (it was added at the end) react_codegen_native_target.build_phases.delete(phase) # Insert it before the "Compile Sources" phase react_codegen_native_target.build_phases.insert(compile_sources_index, phase) else Pod::UI.puts "[Expo] ".yellow + "Could not find 'Compile Sources' phase, build phase added at default position" end end else Pod::UI.puts "[Expo] ".yellow + "ReactCodegen target not found in pods project" end end define_method(:run_podfile_pre_install_hooks) do # Call original implementation first _original_run_podfile_pre_install_hooks.bind(self).() return unless should_disable_use_frameworks_for_core_expo_pods?() # Disable USE_FRAMEWORKS in core targets when USE_FRAMEWORKS is set # This method overrides the build_type field to always use static_library for # the following pod targets: # - ExpoModulesCore, Expo, ReactAppDependencyProvider, expo-dev-menu # These are all including files from React Native Core in their public header files, # which causes their own modular headers to be invalid. Pod::UI.puts "[Expo] ".blue + "Disabling USE_FRAMEWORKS for modules #{@podfile.framework_modules_to_patch.join(', ')}" self.pod_targets.each do |t| if @podfile.framework_modules_to_patch.include?(t.name) def t.build_type Pod::BuildType.static_library end end end end private # We should only disable USE_FRAMEWORKS for specific pods when: # - RCT_USE_PREBUILT_RNCORE is not '1' # - USE_FRAMEWORKS is not set def should_disable_use_frameworks_for_core_expo_pods?() return false if ENV['RCT_USE_PREBUILT_RNCORE'] != '1' return true if get_linkage?() != nil false end # Returns the linkage type if USE_FRAMEWORKS is set, otherwise returns nil def get_linkage?() return nil if ENV["USE_FRAMEWORKS"] == nil return :dynamic if ENV["USE_FRAMEWORKS"].downcase == 'dynamic' return :static if ENV["USE_FRAMEWORKS"].downcase == 'static' nil end end end