当一个大型的移动开发项目内有多个 Target 的时候,如果需要 动态地控制不同 Target 引入的依赖,我们该怎么做?
在此基础上,如果还需要 用条件编译来控制某些代码只在某些 Target 中引入,我们该怎么做?
如果,还要 允许 CI 动态地配置条件编译,我们又该怎么做?
Ficow在这里为你提供一种实现思路,希望对你有所帮助~
本文中涉及到的代码,可以在这里获取:UpdateXcodeprojDemo。建议读者拉取示例代码,运行该项目并查看实际效果。
针对原始的需求,我们需要先进行细化:
CN-APP
, UK-APP
, US-APP
,假设要引入的依赖库叫做 FICOW
。FICOW
这个库。FICOW
的 target,要在编译时期启用相应的条件编译常量。例如,将条件编译常量定义为 FICOW_ON
。FICOW
的 target,就不启用 FICOW_ON
。如果已经启用了,那就要移除 FICOW_ON
。FICOW
和启用条件编译常量 FICOW_ON
。
既然,最终都要从文件输入配置,那我们就先从读取文件开始吧。
首先,我们可以在 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 是否引入。继续吧~
这里的代码比较简单,直接看注释就可以理解:
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 是否引入 FICOW
这个依赖库。
接下来,我们需要考虑修改 2 个和条件编译相关的设置项:
SWIFT_ACTIVE_COMPILATION_CONDITIONS
GCC_PREPROCESSOR_DEFINITIONS
首先,要改 Xcode 项目的配置,我们就要编辑 .xcodeproj 文件。
千万不要重复造轮子,Cocoapods 是基于 Ruby 编写而成,我们有很多 Ruby 的资源可以用。
比如,这里有一个现成的库:
https://github.com/CocoaPods/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
之后,我们就可以到项目里查看相关的配置结果了。
然后,我们就可以在代码里面使用 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