require_relative 'helper/podfile_options' require_relative 'helper/feature_switches' require_relative 'helper/prebuild_sandbox' require_relative 'helper/passer' require_relative 'helper/names' require_relative 'helper/target_checker' # NOTE: # This file will only be loaded on normal pod install step # so there's no need to check is_prebuild_stage # Provide a special "download" process for prebuilded pods. # # As the frameworks is already exsited in local folder. We # just create a symlink to the original target folder. # module Pod class Installer class PodSourceInstaller def install_for_prebuild!(standard_sanbox) return if standard_sanbox.local? self.name # make a symlink to target folder prebuild_sandbox = Pod::PrebuildSandbox.from_standard_sandbox(standard_sanbox) # if spec used in multiple platforms, it may return multiple paths target_names = prebuild_sandbox.existed_target_names_for_pod_name(self.name) def walk(path, &action) return unless path.exist? path.children.each do |child| result = action.call(child, &action) if child.directory? walk(child, &action) if result end end end def make_link(source, target, uselink) source = Pathname.new(source) target = Pathname.new(target) target.parent.mkpath unless target.parent.exist? relative_source = source.relative_path_from(target.parent) if uselink FileUtils.ln_sf(relative_source, target) else if File.directory?(source) FileUtils.cp_r source, target, :remove_destination => true else FileUtils.cp source, target end if not target.exist? raise "资源导入失败:#{target}" end end end def mirror_with_symlink(source, basefolder, target_folder, uselink) target = target_folder + source.relative_path_from(basefolder) make_link(source, target, uselink) end target_names.each do |name| # symbol link copy all substructure real_file_folder = prebuild_sandbox.framework_folder_path_for_target_name(name) # If have only one platform, just place int the root folder of this pod. # If have multiple paths, we use a sperated folder to store different # platform frameworks. e.g. AFNetworking/AFNetworking-iOS/AFNetworking.framework target_folder = standard_sanbox.pod_dir(self.name) if target_names.count > 1 target_folder += real_file_folder.basename end target_folder.rmtree if target_folder.exist? target_folder.mkpath walk(real_file_folder) do |child| source = child # only make symlink to file and `.framework` folder if child.directory? if [".framework"].include? child.extname mirror_with_symlink(source, real_file_folder, target_folder, true) next false # return false means don't go deeper elsif [".dSYM"].include? child.extname mirror_with_symlink(source, real_file_folder, target_folder, false) next false # return false means don't go deeper elsif [".bundle"].include? child.extname mirror_with_symlink(source, real_file_folder, target_folder, false) next false else next true end elsif child.file? mirror_with_symlink(source, real_file_folder, target_folder, false) next true else next true end end # symbol link copy resource for static framework hash = Prebuild::Passer.resources_to_copy_for_static_framework || {} path_objects = hash[name] if path_objects != nil path_objects.each do |object| if object.real_file_path != nil real_path = Pathname.new(object.target_file_path) real_path.rmtree if real_path.exist? make_link(object.real_file_path, object.target_file_path, false) end end end end # of for each end # of method end end end # Let cocoapods use the prebuild framework files in install process. # # the code only effect the second pod install process. # module Pod class Installer # Remove the old target files if prebuild frameworks changed def remove_target_files_if_needed changes = Pod::Prebuild::Passer.prebuild_pod_targets_changes updated_names = [] if changes == nil updated_names = PrebuildSandbox.from_standard_sandbox(self.sandbox).exsited_framework_pod_names else t_changes = Pod::Prebuild::Passer.prebuild_pods_changes added = t_changes.added changed = t_changes.changed deleted = t_changes.deleted updated_names = (added + changed + deleted).to_a updated_names = (changes + updated_names).uniq end updated_names.each do |name| root_name = Specification.root_name(name) next if self.sandbox.local?(root_name) # delete the cached files target_path = self.sandbox.pod_dir(root_name) target_path.rmtree if target_path.exist? support_path = sandbox.target_support_files_dir(root_name) support_path.rmtree if support_path.exist? end end def save_change_targets! sandbox_path = sandbox.root existed_framework_folder = sandbox.generate_framework_path if local_manifest != nil changes = prebuild_pods_changes added = changes.added changed = changes.changed unchanged = changes.unchanged deleted = changes.deleted.to_a existed_framework_folder.mkdir unless existed_framework_folder.exist? exsited_framework_pod_names = sandbox.exsited_framework_pod_names # additions missing = unchanged.select do |pod_name| not exsited_framework_pod_names.include?(pod_name) end # 保存有改变的target列表 root_names_to_update = (added + changed + missing).uniq updates_target_names = (root_names_to_update + deleted).uniq cache = [] updates_targets = [] updates_target_names.each do |pod_name| tars = Pod.fast_get_targets_for_pod_name(pod_name, self.pod_targets, cache) if tars.nil? tars = [] end updates_targets = (updates_targets + tars).uniq end updates_dependency_targets = updates_targets.map {|t| t.recursive_dependent_targets }.flatten.uniq || [] dependency_names = updates_dependency_targets.map { |e| e.pod_name } if Pod::Prebuild::Passer.prebuild_pod_targets_changes.nil? Pod::Prebuild::Passer.prebuild_pod_targets_changes = (updates_target_names + dependency_names).uniq else Pod::Prebuild::Passer.prebuild_pod_targets_changes = (Pod::Prebuild::Passer.prebuild_pod_targets_changes + updates_target_names + dependency_names).uniq end end end # Modify specification to use only the prebuild framework after analyzing old_method2 = instance_method(:resolve_dependencies) define_method(:resolve_dependencies) do if Pod::is_prebuild_stage # call original old_method2.bind(self).() self.save_change_targets! else # Remove the old target files, else it will not notice file changes self.remove_target_files_if_needed # call original old_method2.bind(self).() # ... # ... # ... # after finishing the very complex orginal function # check the pods # Although we have did it in prebuild stage, it's not sufficient. # Same pod may appear in another target in form of source code. # Prebuild.check_one_pod_should_have_only_one_target(self.prebuild_pod_targets) self.validate_every_pod_only_have_one_form # prepare cache = [] def add_vendered_framework(spec, platform, added_framework_file_path) platform_map = spec.attributes_hash[platform] if platform_map == nil platform_map = {} end vendored_frameworks = platform_map["vendored_frameworks"] || [] vendored_frameworks = [vendored_frameworks] if vendored_frameworks.kind_of?(String) vf = spec.attributes_hash["vendored_frameworks"] || [] vf = [vf] if vf.kind_of?(String) vendored_frameworks += vf vendored_frameworks += [added_framework_file_path] spec.attributes_hash["vendored_frameworks"] = vendored_frameworks if spec.attributes_hash[platform] != nil spec.attributes_hash[platform].delete("vendored_frameworks") end end def empty_source_files(spec) spec.attributes_hash["source_files"] = [] ["ios", "watchos", "tvos", "osx"].each do |plat| if spec.attributes_hash[plat] != nil spec.attributes_hash[plat]["source_files"] = [] end end end specs = self.analysis_result.specifications prebuilt_specs = (specs.select do |spec| self.prebuild_pod_names.include? spec.root.name end) prebuilt_specs.each do |spec| # Use the prebuild framworks as vendered frameworks # get_corresponding_targets targets = Pod.fast_get_targets_for_pod_name(spec.root.name, self.pod_targets, cache) targets.each do |target| # the framework_file_path rule is decided when `install_for_prebuild`, # as to compitable with older version and be less wordy. framework_file_path = target.framework_name framework_file_path = target.name + "/" + framework_file_path if targets.count > 1 add_vendered_framework(spec, target.platform.name.to_s, framework_file_path) end # Clean the source files # we just add the prebuilt framework to specific platform and set no source files # for all platform, so it doesn't support the sence that 'a pod perbuild for one # platform and not for another platform.' empty_source_files(spec) # to remove the resurce bundle target. # When specify the "resource_bundles" in podspec, xcode will generate a bundle # target after pod install. But the bundle have already built when the prebuit # phase and saved in the framework folder. We will treat it as a normal resource # file. if spec.attributes_hash["resource_bundles"] bundle_names = spec.attributes_hash["resource_bundles"].keys spec.attributes_hash["resource_bundles"] = nil spec.attributes_hash["resources"] ||= [] spec.attributes_hash["resources"] += bundle_names.map{|n| n+".bundle"} elsif spec.attributes_hash['ios'] && spec.attributes_hash['ios']["resource_bundles"] bundle_names = spec.attributes_hash['ios']["resource_bundles"].keys spec.attributes_hash['ios']["resource_bundles"] = nil spec.attributes_hash['ios']["resources"] ||= [] spec.attributes_hash['ios']["resources"] += bundle_names.map{|n| n+".bundle"} end # to avoid the warning of missing license spec.attributes_hash["license"] = {} end end end # Override the download step to skip download and prepare file in target folder # linpeng edit: hook集成安装源码的方法(源码下载完之后会将源码关联引用到Pod工程) old_method = instance_method(:install_source_of_pod) define_method(:install_source_of_pod) do |pod_name| if Pod::is_prebuild_stage tmp = old_method.bind(self).(pod_name) else # copy from original pod_installer = create_pod_installer(pod_name) # \copy from original if self.prebuild_pod_names.include? pod_name #linpeng edit: Pod::is_prebuild_stage 编译完成后 还会执行该步骤 该步骤会将_Prebuild/GenerateFramework/xxxx目录关联引用到Pod/xxxx目录里面 替换framework原本的源码 pod_installer.install_for_prebuild!(self.sandbox) else pod_installer.install! end # copy from original return @installed_specs.concat(pod_installer.specs_by_platform.values.flatten.uniq) # \copy from original end end end end # A fix in embeded frameworks script. # # The framework file in pod target folder is a symblink. The EmbedFrameworksScript use `readlink` # to read the read path. As the symlink is a relative symlink, readlink cannot handle it well. So # we override the `readlink` to a fixed version. # module Pod module Generator class EmbedFrameworksScript old_method = instance_method(:script) define_method(:script) do script = old_method.bind(self).() if not Pod::is_prebuild_stage patch = <<-SH.strip_heredoc #!/bin/sh # ---- this is added by cocoapods-xlbuild --- # Readlink cannot handle relative symlink well, so we override it to a new one # If the path isn't an absolute path, we add a realtive prefix. old_read_link=`which readlink` readlink () { path=`$old_read_link "$1"`; if [ $(echo "$path" | cut -c 1-1) = '/' ]; then echo $path; else echo "`dirname $1`/$path"; fi } # --- SH # patch the rsync for copy dSYM symlink script = script.gsub "rsync --delete", "rsync --copy-links --delete" script = patch + script end script end end end end