# Copyright 2021 Spotify AB
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'cocoapods'
require 'cocoapods/resolver'
require 'open-uri'
require 'yaml'
require 'json'
require 'pathname'


module CocoapodsXCRemoteCacheModifier
  # Registers for CocoaPods plugin hooks
  module Hooks
    BIN_DIR = '.rc'
    LLDB_INIT_COMMENT="#RemoteCacheCustomSourceMap"
    LLDB_INIT_PATH = "#{ENV['HOME']}/.lldbinit"
    FAT_ARCHIVE_NAME_INFIX = 'arm64-x86_64'
    XCRC_COOCAPODS_ROOT_KEY = 'XCRC_COOCAPODS_ROOT'

    # List of plugins' user properties that should not be copied to .rcinfo
    CUSTOM_CONFIGURATION_KEYS = [
      'enabled',
      'xcrc_location',
      'exclude_targets',
      'exclude_build_configurations',
      'final_target',
      'check_build_configuration',
      'check_platform',
      'modify_lldb_init',
      'fake_src_root',
      'exclude_sdks_configurations'
    ]

    class XCRemoteCache
      @@configuration = nil

      def self.configure(c)
        @@configuration = c
      end

      def self.set_configuration_default_values
        default_values = {
          'mode' => 'consumer',
          'enabled' => true,
          'xcrc_location' => "XCRC",
          'exclude_build_configurations' => [],
          'check_build_configuration' => 'Debug',
          'check_platform' => 'iphonesimulator',
          'modify_lldb_init' => true,
          'xccc_file' => "#{BIN_DIR}/xccc",
          'remote_commit_file' => "#{BIN_DIR}/arc.rc",
          'exclude_targets' => [],
          'prettify_meta_files' => false,
          'fake_src_root' => "/#{'x' * 10 }",
          'disable_certificate_verification' => false,
          'custom_rewrite_envs' => [],
          'exclude_sdks_configurations' => []
        }
        @@configuration.merge! default_values.select { |k, v| !@@configuration.key?(k) }
        # Always include XCRC_COOCAPODS_ROOT_KEY in custom_rewrite_envs
        unless @@configuration['custom_rewrite_envs'].include?(XCRC_COOCAPODS_ROOT_KEY)
          @@configuration['custom_rewrite_envs'] << XCRC_COOCAPODS_ROOT_KEY
        end
      end

      def self.validate_configuration()
        required_values = [
          'cache_addresses',
          'primary_repo',
          'check_build_configuration',
          'check_platform'
        ]

        missing_configuration_values = required_values.select { |v| !@@configuration.key?(v) }
        unless missing_configuration_values.empty?
          throw "XCRemoteCache not fully configured. Make sure all required fields are provided. Missing fields are: #{missing_configuration_values.join(', ')}."
        end

        mode = @@configuration['mode']
        unless mode == 'consumer' || mode == 'producer' || mode == 'producer-fast'
          throw "Incorrect 'mode' value. Allowed values are ['consumer', 'producer', 'producer-fast'], but you provided '#{mode}'. A typo?"
        end

        unless mode == 'consumer' || @@configuration.key?('final_target')
          throw "Missing 'final_target' value in the Pod configuration."
        end
      end

      def self.generate_rcinfo()
        @@configuration.select { |key, value| !CUSTOM_CONFIGURATION_KEYS.include?(key) }
      end

      def self.parent_dir(path, parent_count)
        "../" * parent_count + path
      end

      # @param target [Target] target to apply XCRemoteCache
      # @param repo_distance [Integer] distance from the git repo root to the target's $SRCROOT
      # @param xc_location [String] path to the dir with all XCRemoteCache binaries, relative to the repo root
      # @param xc_cc_path [String] path to the XCRemoteCache clang wrapper, relative to the repo root
      # @param mode [String] mode name ('consumer', 'producer', 'producer-fast' etc.)
      # @param exclude_build_configurations [String[]] list of targets that should have disabled remote cache
      # @param final_target [String] name of target that should trigger marking
      # @param exclude_sdks_configurations [String[]] list of sdks that should have disabled remote cache
      def self.enable_xcremotecache(
        target,
        repo_distance,
        xc_location,
        xc_cc_path,
        mode,
        exclude_build_configurations,
        final_target,
        fake_src_root,
        exclude_sdks_configurations,
        enable_swift_driver_integration
      )
        srcroot_relative_xc_location = parent_dir(xc_location, repo_distance)
        # location of the entrite CocoaPods project, relative to SRCROOT
        srcroot_relative_project_location = parent_dir('', repo_distance)

        target.build_configurations.each do |config|
          # apply only for relevant Configurations
          next if exclude_build_configurations.include?(config.name)
          if mode == 'consumer'
            reset_build_setting(config.build_settings, 'CC', "$SRCROOT/#{parent_dir(xc_cc_path, repo_distance)}", exclude_sdks_configurations)
          elsif mode == 'producer' || mode == 'producer-fast'
            config.build_settings.delete('CC') if config.build_settings.key?('CC')
          end
          swiftc_name = enable_swift_driver_integration ? 'swiftc' : 'xcswiftc'
          reset_build_setting(config.build_settings, 'SWIFT_EXEC', "$SRCROOT/#{srcroot_relative_xc_location}/#{swiftc_name}", exclude_sdks_configurations)
          reset_build_setting(config.build_settings, 'LIBTOOL', "$SRCROOT/#{srcroot_relative_xc_location}/xclibtool", exclude_sdks_configurations)
          # Setting LIBTOOL to '' breaks SwiftDriver intengration so resetting it to the original value 'libtool' for all excluded configurations
          add_build_setting_for_sdks(config.build_settings, 'LIBTOOL', 'libtool', exclude_sdks_configurations)
          reset_build_setting(config.build_settings, 'LD', "$SRCROOT/#{srcroot_relative_xc_location}/xcld", exclude_sdks_configurations)
          reset_build_setting(config.build_settings, 'LDPLUSPLUS', "$SRCROOT/#{srcroot_relative_xc_location}/xcldplusplus", exclude_sdks_configurations)
          reset_build_setting(config.build_settings, 'LIPO', "$SRCROOT/#{srcroot_relative_xc_location}/xclipo", exclude_sdks_configurations)
          reset_build_setting(config.build_settings, 'SWIFT_USE_INTEGRATED_DRIVER', 'NO', exclude_sdks_configurations) unless enable_swift_driver_integration

          reset_build_setting(config.build_settings, 'XCREMOTE_CACHE_FAKE_SRCROOT', fake_src_root, exclude_sdks_configurations)
          reset_build_setting(config.build_settings, 'XCRC_PLATFORM_PREFERRED_ARCH', "$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)", exclude_sdks_configurations)
          reset_build_setting(config.build_settings, XCRC_COOCAPODS_ROOT_KEY, "$SRCROOT/#{srcroot_relative_project_location}", exclude_sdks_configurations)
          debug_prefix_map_replacement = '$(SRCROOT' + ':dir:standardizepath' * repo_distance + ')'
          add_cflags!(config.build_settings, '-fdebug-prefix-map', "#{debug_prefix_map_replacement}=$(XCREMOTE_CACHE_FAKE_SRCROOT)", exclude_sdks_configurations)
          add_swiftflags!(config.build_settings, '-debug-prefix-map', "#{debug_prefix_map_replacement}=$(XCREMOTE_CACHE_FAKE_SRCROOT)", exclude_sdks_configurations)
          delete_build_setting(config.build_settings, 'XCRC_DISABLED')
          add_build_setting_for_sdks(config.build_settings, 'XCRC_DISABLED', 'YES', exclude_sdks_configurations)
        end

        # Prebuild
        if mode == 'consumer'
          existing_prebuild_script = target.build_phases.detect do |phase|
            if phase.respond_to?(:name)
              phase.name != nil && phase.name.start_with?("[XCRC] Prebuild")
            end
          end

          prebuild_script = existing_prebuild_script || target.new_shell_script_build_phase("[XCRC] Prebuild #{target.name}")
          prebuild_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\""
          prebuild_script.input_paths = ["$SRCROOT/#{srcroot_relative_xc_location}/xcprebuild"]
          prebuild_script.output_paths = [
            "$(TARGET_TEMP_DIR)/rc.enabled",
            "$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)"
          ]
          prebuild_script.dependency_file = "$(TARGET_TEMP_DIR)/prebuild.d"

          # Move prebuild (last element) to the position before compile sources phase (to make it real 'prebuild')
          if !existing_prebuild_script
            compile_phase_index = target.build_phases.index(target.source_build_phase)
            target.build_phases.insert(compile_phase_index, target.build_phases.delete(prebuild_script))
          end
        elsif mode == 'producer' || mode == 'producer-fast'
          # Delete existing prebuild build phase (to support switching between modes)
          target.build_phases.delete_if do |phase|
            if phase.respond_to?(:name)
              phase.name != nil && phase.name.start_with?("[XCRC] Prebuild")
            end
          end
        end

        # Postbuild
        existing_postbuild_script = target.build_phases.detect do |phase|
          if phase.respond_to?(:name)
            phase.name != nil && phase.name.start_with?("[XCRC] Postbuild")
          end
        end
        postbuild_script = existing_postbuild_script || target.new_shell_script_build_phase("[XCRC] Postbuild #{target.name}")
        postbuild_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\""
        postbuild_script.input_paths = ["$SRCROOT/#{srcroot_relative_xc_location}/xcpostbuild"]
        postbuild_script.output_paths = [
          "$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(XCRC_PLATFORM_PREFERRED_ARCH).swiftmodule.md5",
          "$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(XCRC_PLATFORM_PREFERRED_ARCH)-$(LLVM_TARGET_TRIPLE_VENDOR)-$(SWIFT_PLATFORM_TARGET_PREFIX)$(LLVM_TARGET_TRIPLE_SUFFIX).swiftmodule.md5"
        ]
        postbuild_script.dependency_file = "$(TARGET_TEMP_DIR)/postbuild.d"
        # Move postbuild (last element) to the position after compile sources phase (to make it real 'postbuild')
        if !existing_postbuild_script
          compile_phase_index = target.build_phases.index(target.source_build_phase)
          target.build_phases.insert(compile_phase_index + 1, target.build_phases.delete(postbuild_script))
        end

        # Mark a sha as ready for a given platform and configuration when building the final_target
        if (mode == 'producer' || mode == 'producer-fast') && target.name == final_target
          existing_mark_script = target.build_phases.detect do |phase|
            if phase.respond_to?(:name)
              phase.name != nil && phase.name.start_with?("[XCRC] Mark")
            end
          end
          mark_script = existing_mark_script || target.new_shell_script_build_phase("[XCRC] Mark")
          mark_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\" mark --configuration \"$CONFIGURATION\" --platform $PLATFORM_NAME"
          mark_script.input_paths = ["$SRCROOT/#{srcroot_relative_xc_location}/xcprepare"]
        else
          # Delete existing mark build phase (to support switching between modes or changing the final target)
          target.build_phases.delete_if do |phase|
            if phase.respond_to?(:name)
              phase.name != nil && phase.name.start_with?("[XCRC] Mark")
            end
          end
        end
      end

      def self.disable_xcremotecache_for_target(target)
        target.build_configurations.each do |config|
          config.build_settings.delete('CC') if config.build_settings.key?('CC')
          config.build_settings.delete('SWIFT_EXEC') if config.build_settings.key?('SWIFT_EXEC')
          config.build_settings.delete('LIBTOOL') if config.build_settings.key?('LIBTOOL')
          config.build_settings.delete('LIPO') if config.build_settings.key?('LIPO')
          config.build_settings.delete('LD') if config.build_settings.key?('LD')
          config.build_settings.delete('LDPLUSPLUS') if config.build_settings.key?('LDPLUSPLUS')
          config.build_settings.delete('SWIFT_USE_INTEGRATED_DRIVER') if config.build_settings.key?('SWIFT_USE_INTEGRATED_DRIVER')
          # Remove Fake src root for ObjC & Swift
          config.build_settings.delete('XCREMOTE_CACHE_FAKE_SRCROOT')
          config.build_settings.delete('XCRC_PLATFORM_PREFERRED_ARCH')
          config.build_settings.delete(XCRC_COOCAPODS_ROOT_KEY)
          remove_cflags!(config.build_settings, '-fdebug-prefix-map')
          remove_swiftflags!(config.build_settings, '-debug-prefix-map')
        end

        # User project is not generated from scratch (contrary to `Pods`), delete all previous XCRemoteCache phases
        target.build_phases.delete_if {|phase|
          # Some phases (e.g. PBXSourcesBuildPhase) don't have strict name check respond_to?
          if phase.respond_to?(:name)
              phase.name != nil && phase.name.start_with?("[XCRC]")
          end
        }
      end

      # Writes XCRemoteCache info in the specified directory location
      def self.save_rcinfo(info, directory)
          File.open(File.join(directory, '.rcinfo'), 'w') { |file| file.write info.to_yaml }
      end

      def self.download_xcrc_if_needed(local_location)
        required_binaries = ['xcld', 'xcldplusplus', 'xclibtool', 'xclipo', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc']
        binaries_exist = required_binaries.reduce(true) do |exists, filename|
          file_path = File.join(local_location, filename)
          exists = exists && File.exist?(file_path)
        end

        # Don't download XCRemoteCache if provided directory already contains it
        return if binaries_exist

        Dir.mkdir(local_location) unless File.exist?(local_location)
        local_package_location = File.join(local_location, 'package.zip')

        download_latest_xcrc_release(local_package_location)

        if !system("unzip #{local_package_location} -d #{local_location}")
          throw "Unzipping XCRemoteCache failed"
        end
      end

      def self.download_latest_xcrc_release(local_package_location)
        release_url = 'https://api.github.com/repos/spotify/XCRemoteCache/releases/latest'
        asset_url = nil

        URI.open(release_url) do |f|
          assets_array = JSON.parse(f.read)['assets']
          # Pick fat archive
          asset_array = assets_array.detect{|arr| arr['name'].include?(FAT_ARCHIVE_NAME_INFIX)}
          asset_url = asset_array['url']
        end

        if asset_url.nil?
          throw "Downloading XCRemoteCache failed"
        end

        URI.open(asset_url, "accept" => 'application/octet-stream') do |f|
          File.open(local_package_location, "wb") do |file|
            file.puts f.read
          end
        end
      end

      def self.add_cflags!(options, key, value, exclude_sdks_configurations)
        reset_build_setting(options, 'OTHER_CFLAGS', remove_cflags!(options, key) << "#{key}=#{value}", exclude_sdks_configurations)
      end

      def self.remove_cflags!(options, key)
        cflags_arr = options.fetch('OTHER_CFLAGS', ['$(inherited)'])
        cflags_arr = [cflags_arr] if cflags_arr.kind_of? String
        options['OTHER_CFLAGS'] = cflags_arr.delete_if {|flag| flag.include?("#{key}=") }
        options['OTHER_CFLAGS']
      end

      def self.add_swiftflags!(options, key, value, exclude_sdks_configurations)
        reset_build_setting(options, 'OTHER_SWIFT_FLAGS', remove_swiftflags!(options, key) + " #{key} #{value}", exclude_sdks_configurations)
      end

      def self.remove_swiftflags!(options, key)
        options['OTHER_SWIFT_FLAGS'] = options.fetch('OTHER_SWIFT_FLAGS', '$(inherited)').gsub(/\s+#{Regexp.escape(key)}\s+\S+/, '')
        options['OTHER_SWIFT_FLAGS']
      end

      def self.add_build_setting(build_settings, key, value, exclude_sdks_configurations)
        build_settings[key] = value
        for exclude_sdks_configuration in exclude_sdks_configurations
          build_settings["#{key}[sdk=#{exclude_sdks_configuration}]"] = [""]
        end
      end

      # Deletes all previous build settings for a key, and sets a new value to all configurations
      # but the sdks in exclude_sdks_configurations
      def self.reset_build_setting(build_settings, key, value, exclude_sdks_configurations)
        delete_build_setting(build_settings, key)
        add_build_setting(build_settings, key, value, exclude_sdks_configurations)
      end

      # Delete all build setting for a key, including settings like "[skd=*,arch=*]"
      def self.delete_build_setting(build_settings, key)
        for build_setting_key in build_settings.keys
          build_settings.delete(build_setting_key) if build_setting_key == key || build_setting_key.start_with?("#{key}[")
        end
      end

      # Sets value for a key only for a subset of sdk configurations
      def self.add_build_setting_for_sdks(build_settings, key, value, sdk_configurations)
        for sdk_configuration in sdk_configurations
          build_settings["#{key}[sdk=#{sdk_configuration}]"] = value
        end
      end

      # Uninstall the XCRemoteCache
      def self.disable_xcremotecache(user_project, pods_project = nil)
        user_project.targets.each do |target|
          disable_xcremotecache_for_target(target)
        end
        user_project.save()

        unless pods_project.nil?
          pods_project.native_targets.each do |target|
            disable_xcremotecache_for_target(target)
          end
          pods_proj_directory = pods_project.project_dir
          pods_project.root_object.project_references.each do |subproj_ref|
            generated_project = Xcodeproj::Project.open("#{pods_proj_directory}/#{subproj_ref[:project_ref].path}")
            generated_project.native_targets.each do |target|
              disable_xcremotecache_for_target(target)
            end
            generated_project.save()
          end
          pods_project.save()
        end

        # Remove .lldbinit rewrite
        save_lldbinit_rewrite(nil,nil) unless !@@configuration['modify_lldb_init']
      end

      # Returns the content (array of lines) of the lldbinit with stripped XCRemoteCache rewrite
      def self.clean_lldbinit_content(lldbinit_path)
        all_lines = []
        return all_lines unless File.exist?(lldbinit_path)
        File.open(lldbinit_path) { |file|
          while(line = file.gets) != nil
            line = line.strip
            if line == LLDB_INIT_COMMENT
              # skip current and next lines
              file.gets
              next
            end
            all_lines << line
          end
        }
        all_lines
      end

      # Append source rewrite command to the lldbinit content
      def self.add_lldbinit_rewrite(lines_content, user_proj_directory,fake_src_root)
        all_lines = lines_content.clone
        all_lines << LLDB_INIT_COMMENT
        all_lines << "settings set target.source-map #{fake_src_root} #{user_proj_directory}"
        all_lines << ""
        all_lines
      end

      def self.save_lldbinit_rewrite(user_proj_directory,fake_src_root)
        lldbinit_lines = clean_lldbinit_content(LLDB_INIT_PATH)
        lldbinit_lines = add_lldbinit_rewrite(lldbinit_lines, user_proj_directory,fake_src_root) unless user_proj_directory.nil?
        File.write(LLDB_INIT_PATH, lldbinit_lines.join("\n"), mode: "w")
      end

      # Contrary to AbstractTarget.source_build_phase, it only finds a build phase, without creating one if it doesn't exist
      def self.find_source_build_phase(target)
        target.build_phases.find { |bp| bp.class == Xcodeproj::Project::Object::PBXSourcesBuildPhase }
      end

      Pod::HooksManager.register('cocoapods-xcremotecache', :pre_install) do |installer_context|
        # The main responsibility of that hook is forcing Pods regeneration when XCRemoteCache is enabled for the first time
        # In the post_install hook, this plugin adds extra build settings and steps to all Pods targets, but only when XCRemoteCache
        # is enabled and all artifacts are available (i.e. xcprepare returns 0).
        # If Pods projects/targets are cached from previous `pod install` action that didn't enable XCRemoteCache (e.g. artifacts
        # are not available in the remote cache), these projects/targets should be invalidated to include XCRemoteCache-related
        # build steps and build settings.
        if @@configuration.nil?
          Pod::UI.puts "[XCRC] Warning! XCRemoteCache not configured. Call xcremotecache({...}) in Podfile to enable XCRemoteCache"
          next
        end

        begin
          # `user_pod_directory`` and `user_proj_directory` in the 'postinstall' should be equal
          user_pod_directory = File.dirname(installer_context.podfile.defined_in_file)
          set_configuration_default_values

          unless @@configuration['enabled']
            # No need to check if enabling remote cache for the first time
            next
          end

          validate_configuration()
          mode = @@configuration['mode']
          remote_commit_file = @@configuration['remote_commit_file']
          xcrc_location = @@configuration['xcrc_location']
          check_build_configuration = @@configuration['check_build_configuration']
          check_platform = @@configuration['check_platform']

          xcrc_location_absolute = "#{user_pod_directory}/#{xcrc_location}"
          remote_commit_file_absolute = "#{user_pod_directory}/#{remote_commit_file}"

          # Download XCRC
          download_xcrc_if_needed(xcrc_location_absolute)

          # Save .rcinfo
          root_rcinfo = generate_rcinfo()
          save_rcinfo(root_rcinfo, user_pod_directory)

          # Create directory for xccc & arc.rc location
          Dir.mkdir(BIN_DIR) unless File.exist?(BIN_DIR)

          # Remove previous xccc & arc.rc
          was_previously_enabled = File.exist?(remote_commit_file_absolute)
          File.delete(remote_commit_file_absolute) if File.exist?(remote_commit_file_absolute)

          prepare_result = YAML.load`#{xcrc_location_absolute}/xcprepare --configuration #{check_build_configuration} --platform #{check_platform}`
          if !prepare_result['result'] && mode == 'consumer'
            # Remote cache is still disabled - no need to force Pods projects/targets regeneration
            next
          end

          # Force rebuilding all Pods project, because XCRC build steps and settings need to be added to Pods project/targets
          # It is relevant only when 'incremental_installation' is enabled, otherwise installed_cache_path does not exist on a disk
          installed_cache_path = installer_context.sandbox.project_installation_cache_path
          if !was_previously_enabled && File.exist?(installed_cache_path)
            Pod::UI.puts "[XCRC] Forces Pods project regenerations because XCRC is enabled for the first time."
            File.delete(installed_cache_path)
          end
        end
      end

      Pod::HooksManager.register('cocoapods-xcremotecache', :post_install) do |installer_context|
        if @@configuration.nil?
          next
        end

        user_project = installer_context.umbrella_targets[0].user_project

        begin
          user_proj_directory = File.dirname(user_project.path)
          set_configuration_default_values

          unless @@configuration['enabled']
            Pod::UI.puts "[XCRC] XCRemoteCache disabled"
            disable_xcremotecache(user_project)
            next
          end

          validate_configuration()
          mode = @@configuration['mode']
          xccc_location = @@configuration['xccc_file']
          remote_commit_file = @@configuration['remote_commit_file']
          xcrc_location = @@configuration['xcrc_location']
          exclude_targets = @@configuration['exclude_targets'] || []
          exclude_build_configurations = @@configuration['exclude_build_configurations'] || []
          final_target = @@configuration['final_target']
          check_build_configuration = @@configuration['check_build_configuration']
          check_platform = @@configuration['check_platform']
          fake_src_root = @@configuration['fake_src_root']
          exclude_sdks_configurations = @@configuration['exclude_sdks_configurations'] || []
          enable_swift_driver_integration = @@configuration['enable_swift_driver_integration'] || false

          xccc_location_absolute = "#{user_proj_directory}/#{xccc_location}"
          xcrc_location_absolute = "#{user_proj_directory}/#{xcrc_location}"

          # Save .rcinfo
          root_rcinfo = generate_rcinfo()
          save_rcinfo(root_rcinfo, user_proj_directory)

          # Remove previous xccc
          File.delete(xccc_location_absolute) if File.exist?(xccc_location_absolute)

          # Prepare XCRC

          # Pods projects can be generated only once (if incremental_installation is enabled)
          # Always integrate XCRemoteCache to all Pods, in case it will be needed later
          unless installer_context.pods_project.nil?
            # Attach XCRemoteCache to Pods targets
            # Enable only for native targets which can have compilation steps
            installer_context.pods_project.native_targets.each do |target|
                # Ensure the PBXSourcesBuildPhase exists as the flow would unnecessary create an empty source build phase otherwise
                next if find_source_build_phase(target).nil?
                next if target.source_build_phase.files_references.empty?
                next if target.name.start_with?("Pods-")
                next if target.name.end_with?("Tests")
                next if exclude_targets.include?(target.name)
                enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root, exclude_sdks_configurations, enable_swift_driver_integration)
            end

            # Create .rcinfo into `Pods` directory as that .xcodeproj reads configuration from .xcodeproj location
            pods_proj_directory = installer_context.sandbox_root

            # Attach XCRemoteCache to Generated Pods projects
            installer_context.pods_project.root_object.project_references.each do |subproj_ref|
                generated_project = Xcodeproj::Project.open("#{pods_proj_directory}/#{subproj_ref[:project_ref].path}")
                generated_project.native_targets.each do |target|
                    next if target.source_build_phase.files_references.empty?
                    next if target.name.end_with?("Tests")
                    next if exclude_targets.include?(target.name)
                    enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root, exclude_sdks_configurations, enable_swift_driver_integration)
                end
                generated_project.save()
            end

            # Manual Pods/.rcinfo generation

            # all paths in .rcinfo are relative to the root so paths used in Pods.xcodeproj need to be aligned
            pods_path = Pathname.new(pods_proj_directory)
            root_path = Pathname.new(user_proj_directory)
            root_path_to_pods = root_path.relative_path_from(pods_path)

            pods_rcinfo = root_rcinfo.merge({
              'remote_commit_file' => "#{root_path_to_pods}/#{remote_commit_file}",
              'xccc_file' => "#{root_path_to_pods}/#{xccc_location}"
            })
            save_rcinfo(pods_rcinfo, pods_proj_directory)

            installer_context.pods_project.save()
          end

          # Enabled/disable XCRemoteCache for the main (user) project
          begin
            # TODO: Do not compile xcc again. `xcprepare` compiles it in pre-install anyway
            prepare_result = YAML.load`#{xcrc_location_absolute}/xcprepare --configuration #{check_build_configuration} --platform #{check_platform}`
            unless prepare_result['result'] || mode != 'consumer'
              # Uninstall the XCRemoteCache for the consumer mode
              disable_xcremotecache(user_project, installer_context.pods_project)
              Pod::UI.puts "[XCRC] XCRemoteCache disabled - no artifacts available"
              next
            end
          rescue => error
            disable_xcremotecache(user_project, installer_context.pods_project)
            Pod::UI.puts "[XCRC] XCRemoteCache failed with an error: #{error}."
            next
          end


          # Attach XCRC to the app targets
          user_project.targets.each do |target|
              next if exclude_targets.include?(target.name)
              enable_xcremotecache(target, 0, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root, exclude_sdks_configurations, enable_swift_driver_integration)
          end

          # Set Target sourcemap
          if @@configuration['modify_lldb_init']
            save_lldbinit_rewrite(user_proj_directory,fake_src_root)
          else
            Pod::UI.puts "[XCRC] lldbinit modification is disabled. Debugging may behave weirdly"
            Pod::UI.puts "[XCRC] put \"settings set target.source-map #{fake_src_root} \#{your_project_directory}\" to your \".lldbinit\" "
          end

          user_project.save()
          Pod::UI.puts "[XCRC] XCRemoteCache enabled"
        rescue Exception => e
          Pod::UI.puts "[XCRC] XCRemoteCache disabled with error: #{e}"
          puts e.full_message(highlight: true, order: :top)
          disable_xcremotecache(user_project, installer_context.pods_project)
        end
      end
    end
  end
end
