使用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