借助 Cocoapods 动态控制 Xcode 项目的依赖引入和条件编译 —— Ficow 的实战笔记

| iOS , Xcode , 实用工具

 

内容概览

  • 前言
  • 细化需求
  • 用文件输入 Target 配置
  • 动态控制 Target 引入的依赖
  • 动态控制 Target 的条件编译
    • 导入 Ruby 库 Xcodeproj
    • 修改 .xcodeproj 中的条件编译配置
  • 检查配置是否正确
  • 总结

 

前言

 

当一个大型的移动开发项目内有多个 Target 的时候,如果需要 动态地控制不同 Target 引入的依赖,我们该怎么做?
在此基础上,如果还需要 用条件编译来控制某些代码只在某些 Target 中引入,我们该怎么做?
如果,还要 允许 CI 动态地配置条件编译,我们又该怎么做?

Ficow在这里为你提供一种实现思路,希望对你有所帮助~

本文中涉及到的代码,可以在这里获取:UpdateXcodeprojDemo。建议读者拉取示例代码,运行该项目并查看实际效果。

 

细化需求

 

针对原始的需求,我们需要先进行细化:

  • 要动态地控制不同 Target 引入的依赖
    假设我们的 App Target 分别为: CN-APP, UK-APP, US-APP,假设要引入的依赖库叫做 FICOW
    那么,这 3 个 target 有可能引入或者不引入 FICOW 这个库。
  • 还要用条件编译来控制某些代码只在某些 Target 中引入
    引入了 FICOW 的 target,要在编译时期启用相应的条件编译常量。例如,将条件编译常量定义为 FICOW_ON
    没有引入 FICOW 的 target,就不启用 FICOW_ON。如果已经启用了,那就要移除 FICOW_ON
  • 还要允许 CI 动态地配置条件编译
    让 CI 直接改 Podfile 也是可行的。但是保险起见,我们用一个单独的文件来控制是否引入依赖库 FICOW 和启用条件编译常量 FICOW_ON

 

用文件输入 Target 配置

 

既然,最终都要从文件输入配置,那我们就先从读取文件开始吧。

首先,我们可以在 Podfile 内定义一个数组记录所有的 Target 名称:

all_targets = ["CN-APP", "UK-APP", "US-APP"]

然后,定义一个数组记录需要引入 FICOW 这个库的 Target 名称:

$ficow_targets = []

$ 用于定义全局变量,之后这个变量就可以用在后文定义的函数里面。

然后,按行读取文件里面的 Target 名称:

all_targets = ["CN-APP", "UK-APP", "US-APP"]
$ficow_targets = []

ficow_config_file = "ficow_enabled_targets.txt" # 配置文件的名称
File.readlines(ficow_config_file).each do |name| # 按行读取文本
  stripped_name = name.strip # strip是一个 Ruby 内置函数,用于移除字符串前后多余的空格
  if !stripped_name.empty? # 如果文本不是空字符串
    $ficow_targets.push(stripped_name) # 加到 FICOW 数组
    puts "Added target #{stripped_name} to ficow_targets" # 打印日志,方便调试
  end
end

到这里,我们已经实现了用文本文件和 Podfile 来控制 FICOW pod 是否引入。继续吧~

 

动态控制 Target 引入的依赖

 

这里的代码比较简单,直接看注释就可以理解:

all_targets.each do |name| # 遍历所有的 target 名称
  target name do
    if $ficow_targets.include?(name) # 文件中配置的 target 包含当前的 target 名称
      pod 'FICOW', :path => './FICOW' # 引入 FICOW pod
    end
  end
end

 

动态控制 Target 的条件编译

 

基于前面的工作,我们已经可以确定某个 Target 是否引入 FICOW 这个依赖库。

接下来,我们需要考虑修改 2 个和条件编译相关的设置项:

  • Swift: SWIFT_ACTIVE_COMPILATION_CONDITIONS
  • ObjC: GCC_PREPROCESSOR_DEFINITIONS

 

导入 Ruby 库 Xcodeproj

 

首先,要改 Xcode 项目的配置,我们就要编辑 .xcodeproj 文件。

千万不要重复造轮子,Cocoapods 是基于 Ruby 编写而成,我们有很多 Ruby 的资源可以用。

比如,这里有一个现成的库:

https://github.com/CocoaPods/Xcodeproj

仓库里面有一些简单的例子,感兴趣的话你可以了解一下。

 

修改 .xcodeproj 中的条件编译配置

 

代码有点多,不过主要就是一个函数:

def enableFICOWForTargets()
  require 'xcodeproj' # 引入 xcodeproj 库
  project_path = 'UpdateXcodeprojDemo.xcodeproj' # 项目文件名
  project = Xcodeproj::Project.open(project_path)
  condition = "FICOW_ON"
  swift_condition_key = "SWIFT_ACTIVE_COMPILATION_CONDITIONS"
  objc_conditon_key = "GCC_PREPROCESSOR_DEFINITIONS"
  project.targets.each do |target|
    # puts "Started processing FICOW for target: #{target.name}"
    # 添加 FICOW_ON
    if $ficow_targets.include?(target.name)
      target.build_configurations.each do |conf|
        # swift
        swift_condition_string = conf.build_settings[swift_condition_key] # 得到一个字符串值
#        puts "swift_condition_string: #{swift_condition_string}, class:#{swift_condition_string.class}, target: #{target.name}, config: #{conf.name}"
        if swift_condition_string.nil? # 旧配置为空
          conf.build_settings[swift_condition_key] = condition
          puts "[Added #{condition} for Swift - nil] swift_conditions: #{conf.build_settings[swift_condition_key]}, target: #{target.name}, config: #{conf.name}"
        else
          unless swift_condition_string.include?(condition)
            conditions = []
            swift_condition_string.split(' ', -1).each do |c|
              stripped_condition = c.strip # 处理多余的空格
              conditions.push(stripped_condition)
            end
            new_condition_string = conditions.push(condition).join(" ")
            conf.build_settings[swift_condition_key] = new_condition_string
            puts "[Added #{condition} for Swift - String] swift_conditions: #{conf.build_settings[swift_condition_key]}, target: #{target.name}, config: #{conf.name}"
          end
        end
        # objc
        objc_conditions = conf.build_settings[objc_conditon_key] # 可能的返回值:nil, 字符串(空字符串、单个元素),数组(多个元素)
#        puts "objc_conditions: #{objc_conditions}, class:#{objc_conditions.class}"
        if objc_conditions.nil? # 旧配置为空
          conf.build_settings[objc_conditon_key] = condition
          puts "[Added #{condition} for ObjC - nil] objc_conditons: #{conf.build_settings[objc_conditon_key]}, target: #{target.name}, config: #{conf.name}"
          # https://stackoverflow.com/a/15769829
        elsif objc_conditions.is_a?(String) # 通常为 $(inherited)
          conf.build_settings[objc_conditon_key] = [objc_conditions, condition]
          puts "[Added #{condition} for ObjC - String] objc_conditons: #{conf.build_settings[objc_conditon_key]}, target: #{target.name}, config: #{conf.name}"
        elsif objc_conditions.is_a?(Array)
          unless objc_conditions.include?(condition)
            conf.build_settings[objc_conditon_key].push(condition)
            puts "[Added #{condition} for ObjC - Array] objc_conditons: #{conf.build_settings[objc_conditon_key]}, target: #{target.name}, config: #{conf.name}"
          end
        end
      end
      # 去除 FICOW_ON
    else
      # swift
      target.build_configurations.each do |conf|
        swift_condition_string = conf.build_settings[swift_condition_key] # 得到一个字符串值
        if !swift_condition_string.nil? && swift_condition_string.include?(condition)
          conditions = []
          swift_condition_string.split(' ', -1).each do |c|
            stripped_condition = c.strip # 处理多余的空格
            if stripped_condition != condition
#              puts "adding #{stripped_condition} to conditions"
              conditions.push(stripped_condition)
            end
          end
          new_condition_string = conditions.join(" ")
          puts "[Removed #{condition} for Swift] swift_conditions: #{new_condition_string}, target: #{target.name}, config: #{conf.name}"
          conf.build_settings[swift_condition_key] = new_condition_string
        end
        # objc
        objc_conditions = conf.build_settings[objc_conditon_key] # 可能的返回值:nil, 字符串(空字符串、单个元素),数组(多个元素)
#        puts "objc_conditions: #{objc_conditions}, class:#{objc_conditions.class}"
        unless objc_conditions.nil?
          if objc_conditions.is_a?(String) && objc_conditions.strip == condition
            conf.build_settings[objc_conditon_key] = ""
            puts "[Removed #{condition} for ObjC - String] objc_conditons: #{conf.build_settings[objc_conditon_key]}, target: #{target.name}, config: #{conf.name}"
          elsif objc_conditions.is_a?(Array) && objc_conditions.include?(condition)
            if objc_conditions.length == 1
              conf.build_settings[objc_conditon_key] = ""
            elsif objc_conditions.length == 2
              objc_conditions.delete(condition)
              # puts "objc_conditions.first(): #{objc_conditions.first}"
              conf.build_settings[objc_conditon_key] = objc_conditions.first
            else
              conf.build_settings[objc_conditon_key].delete(condition)
            end
            puts "[Removed #{condition} for ObjC - Array] objc_conditons: #{conf.build_settings[objc_conditon_key]}, target: #{target.name}, config: #{conf.name}"
          end
        end
      end
    end
  end

  project.save() # 保存改动到 .xcodeproj 文件
  puts "Finished processing FICOW for targets"
end

最后,在这里调用这个函数即可:

post_install do |installer|
  unless $ficow_targets.empty?
    enableFICOWForTargets()
  end
end

注意,post_install 只能定义一个,如果定义多个就会报错。

上面的脚本代码存在一定的可优化空间(代码结构、重复代码、特殊情况处理等),建议读者根据自己的实际需求进行调整。

 

检查配置是否正确

 

CN-APP 启用 FICOW_ON 并执行 pod install 之后,我们就可以到项目里查看相关的配置结果了。

Swift 条件编译配置:

OjbC 条件编译配置:

然后,我们就可以在代码里面使用 FICOW_ON 这个条件编译常量了:

#if FICOW_ON
// Swift Code
#endif
#ifdef FICOW_ON
// Swift Code
#endif

 

总结

 

其实,这里的设计没什么技术含量,主要就是细化需求,然后实现即可。

实战笔记,仅供参考,如果谬误,欢迎指出,谢谢~

总体来说,Ruby还是比较容易上手的。俗话说,技多不压身,会写点脚本还是挺好的,哈哈。

 

参考内容:
CocoaPods/Xcodeproj
How to read lines of a file in Ruby
xcodebuild ACTIVE_COMPILATION_CONDITIONS does not override target’s ACTIVE_COMPILATION_CONDITIONS
Xcode: Setting GCC_PREPROCESSOR_DEFINITIONS for different build configurations?

 

觉得不错?点个赞呗~

本文链接:借助 Cocoapods 动态控制 Xcode 项目的依赖引入和条件编译 —— Ficow 的实战笔记

转载声明:本站文章如无特别说明,皆为原创。转载请注明:Ficow Shen's Blog

评论区(期待你的留言)