使用xcodeproj 动态插入第三方代码

# 为什么这么做?

  现在有这么一个使用场景,基线能生成项目A,项目B,项目C…如果只有项目A中使用SDK_A,其他项目都不使用,这时候就需要对基线进行差分,只有当我切换到项目A时,才插入SDK_A。

  不同于cocoapods的库管理方式,xcodeproj是通过脚本在编译前向项目中插入指定代码文件。

# Xcode中的项目结构

  

 

   项目中都会有个主target作为根节点,底下有很多group节点,这些group节点管理着三类文件

    1. 常规的 .h/.m 文件

    2. 资源文件

    3. 库文件,分为静态库.a文件、动态库.framework文件、阉割版的动态库embed franework文件

## 如何引入

   这里涉及到xcodeproj的具体使用,贴一下官方文档:

#获取项目
$project = Xcodeproj::Project.open($project_path);

#获取target,通常取第一个为项目的主target
$target = $project.targets.first

# 获取插件目录的group,如果不存在则创建
$group_One = $project[$plugin_folder] || $project.main_group.find_subpath(File.join($plugin_folder), true);

# 在目标目录新建group目录
$group = $group_One.find_subpath($folderName, true)
$SDK_PATH = $group_One.real_path.to_s + "/" + $plugin_folder + "/" + $folderName

# 判断SDK_PATH目录下是否存在第三方代码,不存在则退出
if !FileTest::exists?($SDK_PATH)
    puts "SDK file not found in #{$SDK_PATH}"
    exit 1
end

  * project_path 是项目的路径

  * plugin_folder 是插入的目录名称,它是创建在项目目录底下的

  * folder_name 是插入的第三方库的文件夹的名称,它是创建在plugin_folder目录底下的

  

  注意,这里的第三库的代码文件其实和项目的代码是放在一块的,我们要做的只是将它和项目关联起来,也就是在XXX.xcodeproj文件中添加其引用。具体的关联就是建立target底下的group组后 添加上述3类文件的引用。

  1. .h/.m 文件添加引用

if filePath.to_s.end_with?(".h") then
    fileReference = aGroup.new_reference(filePath);
    # aTarget.source_build_phase.add_file_reference(fileReference, true)
elsif filePath.to_s.end_with?(".m", ".mm", ".cpp") then
    fileReference = aGroup.new_reference(filePath);
    aTarget.source_build_phase.add_file_reference(fileReference, true)

   需要注意的是.h文件只需要 在group底下new一个reference,.m文件需要将group底下的reference添加进source_build_phase

  2. 资源文件添加引用

if filePath.to_s.end_with?(".bundle",".plist" ,".xml",".png",".xib",".js",".html",".css",".strings")
    fileReference = aGroup.new_reference(filePath);
    aTarget.resources_build_phase.add_file_reference(fileReference, true)

   根据资源文件创建其group的reference后,需要将其添加进resources_build_phase

  3. 库文件的引用

if filePath.to_s.end_with?(".framework" ,".a")
    fileReference = aGroup.new_reference(filePath);
    build_phase = aTarget.frameworks_build_phase;
    build_phase.add_file_reference(fileReference);
    if $isEmbed == true
        #添加动态库
        $embed_framework.add_file_reference(fileReference)
        #勾上code sign on copy选项(默认是没勾上的)
        $embed_framework.files.each do |file|
            # puts "entry filePath : #{filePath} fileRef path : #{file.file_ref.path}"
            if filePath.end_with?(file.file_ref.path) then
                if file.settings.nil? then
                    # puts "setting is nil"
                    file.settings = Hash.new
                end
                file.settings["ATTRIBUTES"] = ["CodeSignOnCopy", "RemoveHeadersOnCopy"]
            end
        end
    end

   库文件需将 reference 添加进frameworks_build_phase,如果是embed framework还需将其添加进embed_frameworks_build_phase,其在xcodeproj中的具体类型是PBXCopyFilesBuildPhase

 

## 如何清除

  因为SDK_A仅仅是项目A使用,如果从项目A切换到项目B,此时就得从XXX.xcodeproj文件中清除关于SDK_A的所有引用,其实是添加引用的一个逆向过程。

  上文中我们说到 将SDK_A插入{$PROJECT_PATH}/plugin_folder,所以只需遍历这个group,清除其中所有的reference即可。

def removeBuildPhaseFilesRecursively(aTarget, aGroup)
  aGroup.files.each do |file|
      if file.real_path.to_s.end_with?(".m", ".mm", ".cpp") then
          aTarget.source_build_phase.remove_file_reference(file)
      elsif file.real_path.to_s.end_with?(".bundle",".plist" ,".xml",".png",".xib",".js",".html",".css",".strings") then
          aTarget.resources_build_phase.remove_file_reference(file)
      elsif file.real_path.to_s.end_with?(".framework" ,".a")
          aTarget.frameworks_build_phase.remove_file_reference(file)
          # remove embed ref
          if $isEmbed && !$embed_framework.nil?
            $embed_framework.remove_file_reference(file)
          end
      end

      # extra r+emove file ref
      file.remove_from_project
  end
  
  aGroup.groups.each do |group|
      # puts "group path : #{group.path}"
      if group.path == "embed"
          $isEmbed = true
      end
      removeBuildPhaseFilesRecursively(aTarget, group)
      $isEmbed = false
  end
end

   * .m 文件的引用由source_build_phase移除

   * 资源文件的引用由resources_build_phase移除

   * 库文件的引用由frameworks_build_phase移除,其中embed的framework还需由embed_framework_build_phase额外移除一下

# 后记

  xcodeproj 其实不光只是添加引用,xcode中的build_settings的所有选项几乎都可以通过xcodeproj的脚本控制,这里先按下不表。

  最后附一下 github的传送门地址:

  //github.com/xuanyuelin/ManuallyInsertCodes