Jul
18
我们通过ruby做自动化与CI流程时,往往会用到xcodeproj即https://github.com/CocoaPods/Xcodeproj这个库,毕竟半个iOS圈都在用它做自动化CI,因此我们考虑通过脚本为项目插入代码与资源时,也发现xcodeproj即可满足诉求。本篇小记一下通过xcodeproj插入代码与资源,并且在构建过程做手脚。
插入代码
普通的插入代码文件的方式相当直接,举个例子,将something这个写入代码加入项目的auto_routers目录中,并加入构建:
这一段代码相当清晰了,不过有一些细节其实经过多次尝试才确认的:
a. @filepathA与@filepathB即实际写入file_ref的文件地址,其实是相对project的物理目录相对路径,即我们的工程是workspace/xxx.xcodeproj,主目录是workspace/xxx/,我们要引入的文件是workspace/xxx/auto_routers/something.h,那么这里丢给group.new_reference适用的filename应该是auto_routers/something.h,因为xxx才是xxx.xcodeproj的项目根目录。
b. 构建过程的信息都是和target绑定的,因此将代码加入构建需要找到target,此处约定了寻找target的方式即target名称和项目名相同,否则还真没有合适的寻找方式。可能可行的一个方法是寻找app类型target。
c. 虽然xxx才是项目的根目录,但是@project.main_group取到的其实是xcode中看到那个目录即最外层目录,在xcode项目的group中,根目录其实是@project.main_group[@project_name],我们习惯性要将文件都加入这里面。
d. target.add_file_references只需要加.m,别弄错了。同时加入构建列表中默认是在最后,相对较为稳妥。毕竟自动生成的代码不太可能被别人依赖。
e. target对象的操作会去重,所以可以无脑加,但是group操作必须优先判断。
插入资源
相对于插入代码,资源的插入大体类似,唯一的区别是最后一步,插入target时:
即可。不过实际操作iOS各类资源文件时,同时要注意的点包括:
a. 处理图片资源的时候,记得加入的是资源目录而不是具体的文件,不要无脑加入。
b. 项目中加入资源可以是重名的,但是最后target编译的时候不稳定会出现意料之外的事情。同时,有一个特殊文件即Info.plist是没有在资源和项目group中的,但是它会展示并且可以独立加入,反正会编译不过。所以名字叫Info.plist的东西需要特殊处理。
c. 刚才说到project中资源可以重名,但是target不行,为什么呢,其实可以利用buildsetting中的EXCLUDED_SOURCE_FILE_NAMES,在不同的构建下对重复资源进行排除,只引入一份,从而实现了不同config引不同资源的效果。
插入其他构建过程
刚才其实说到的往target的编译和资源拷贝过程加入东西。会不会有可能加入其他构建过程呢?更通用的场景,我们很有可能要自动加入一些shell取完成预处理。因此也尝试了加入Shell Script Build Phase的代码:
其实整体也很直接了,一些补充的细节点:
a. new_shell_script_build_phase会默认创建一个shell步骤并且加入target构建过程的最后。
b. 所以一般我们会再往前挪动下, 可以通过名字从@target.build_phases数组中找到一个特定的步骤。build_phase对象to_s后,有name属性的过程即xcode中可以自定义名字过程,会返回名字,否则会粗暴的返回标准名字,那确实有可能有重名。但是一般的项目也不会真的加入两个源码编译步骤或者资源拷贝步骤吧。
c. 实际script对象的属性相当多,整体和xcode看到的一致。
d. 此处@target.build_phases其实可以随便操作构建过程的,不过请稳妥操作,覆水难收。
插入代码
普通的插入代码文件的方式相当直接,举个例子,将something这个写入代码加入项目的auto_routers目录中,并加入构建:
@filepathA = #auto_routers/something.h
@filepathB = #auto_routers/something.m
#首先需要进行实际文件的写入
#并且第一步是准备目录
FileUtils.mkdir_p(File.dirname(@filepathA))
File.open(@filepathA, "w") { |file| file.print file_code_str_A }
File.open(@filepathB, "w") { |file| file.print file_code_str_B }
#找到目标target
target = @project.targets.first
@project.targets.each do |name|
if name == @project_name
target = name
end
end
#准备auto_routers目录
project_group = @project.main_group[@project_name]
auto_routers_group = project_group['auto_routers']
if not auto_routers_group
auto_routers_group = project_group.new_group('auto_routers')
end
#准备文件引用
file_ref_a = auto_routers_group.find_file_by_path(@filenameA)
if not file_ref_a
file_ref_a = auto_routers_group.new_reference(@filenameA)
end
file_ref_b = auto_routers_group.find_file_by_path(@filenameB)
if not file_ref_b
file_ref_b = auto_routers_group.new_reference(@filenameB)
end
#加入target构建
target.add_file_references([file_ref_b])
@project.save
这一段代码相当清晰了,不过有一些细节其实经过多次尝试才确认的:
a. @filepathA与@filepathB即实际写入file_ref的文件地址,其实是相对project的物理目录相对路径,即我们的工程是workspace/xxx.xcodeproj,主目录是workspace/xxx/,我们要引入的文件是workspace/xxx/auto_routers/something.h,那么这里丢给group.new_reference适用的filename应该是auto_routers/something.h,因为xxx才是xxx.xcodeproj的项目根目录。
b. 构建过程的信息都是和target绑定的,因此将代码加入构建需要找到target,此处约定了寻找target的方式即target名称和项目名相同,否则还真没有合适的寻找方式。可能可行的一个方法是寻找app类型target。
c. 虽然xxx才是项目的根目录,但是@project.main_group取到的其实是xcode中看到那个目录即最外层目录,在xcode项目的group中,根目录其实是@project.main_group[@project_name],我们习惯性要将文件都加入这里面。
d. target.add_file_references只需要加.m,别弄错了。同时加入构建列表中默认是在最后,相对较为稳妥。毕竟自动生成的代码不太可能被别人依赖。
e. target对象的操作会去重,所以可以无脑加,但是group操作必须优先判断。
插入资源
相对于插入代码,资源的插入大体类似,唯一的区别是最后一步,插入target时:
target.add_resources([file_ref])
即可。不过实际操作iOS各类资源文件时,同时要注意的点包括:
a. 处理图片资源的时候,记得加入的是资源目录而不是具体的文件,不要无脑加入。
b. 项目中加入资源可以是重名的,但是最后target编译的时候不稳定会出现意料之外的事情。同时,有一个特殊文件即Info.plist是没有在资源和项目group中的,但是它会展示并且可以独立加入,反正会编译不过。所以名字叫Info.plist的东西需要特殊处理。
c. 刚才说到project中资源可以重名,但是target不行,为什么呢,其实可以利用buildsetting中的EXCLUDED_SOURCE_FILE_NAMES,在不同的构建下对重复资源进行排除,只引入一份,从而实现了不同config引不同资源的效果。
插入其他构建过程
刚才其实说到的往target的编译和资源拷贝过程加入东西。会不会有可能加入其他构建过程呢?更通用的场景,我们很有可能要自动加入一些shell取完成预处理。因此也尝试了加入Shell Script Build Phase的代码:
@name = #过程的名字
@before = #要加入在某个过程之前
#准备target
@target = @project.targets.first
@project.targets.each do |name|
if name == project_name
@target = name
end
end
#创建
script = @target.new_shell_script_build_phase(@name)
script.shell_script = shell_code #塞入shell代码
#挪位置,如果声明了before,要把script挪到before之前
if @before
@target.build_phases.delete(script)
after_step = @target.build_phases[0]
@target.build_phases.each do |step|
if step.to_s == @before
after_step = step
end
end
index = @target.build_phases.find_index(after_step)
@target.build_phases.insert(index, script)
end
@project.save
其实整体也很直接了,一些补充的细节点:
a. new_shell_script_build_phase会默认创建一个shell步骤并且加入target构建过程的最后。
b. 所以一般我们会再往前挪动下, 可以通过名字从@target.build_phases数组中找到一个特定的步骤。build_phase对象to_s后,有name属性的过程即xcode中可以自定义名字过程,会返回名字,否则会粗暴的返回标准名字,那确实有可能有重名。但是一般的项目也不会真的加入两个源码编译步骤或者资源拷贝步骤吧。
c. 实际script对象的属性相当多,整体和xcode看到的一致。
d. 此处@target.build_phases其实可以随便操作构建过程的,不过请稳妥操作,覆水难收。