使用xcodeproj 動態插入第三方程式碼

小二黑挖土發表於2021-07-13

# 為什麼這麼做?

  現在有這麼一個使用場景,基線能生成專案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的傳送門地址:

  https://github.com/xuanyuelin/ManuallyInsertCodes

相關文章