1. Objectives and context

Currently, the company has two products, except for some resources, they are not very different, so they are put in the same project and managed by multiple targets. In the maintenance process, new files or images may be missing in one of the targets. If two products are verified each time, the workload will be large.

All configurations are recorded in the.project file, including the resources contained in each Target, which can be checked by parsing the.project file and checking the resources contained in each Target before compilation to find problems.

After a week of practice on this idea. We have been able to check for compilation files, frameworks and copy resources(the configuration in the project below), and resources in.xcasset. We can also set up a list of resources to ignore.

2. Implementation method

2.1. Pbxproj file introduction

The.prxproj file is essentially an old-style Property List file. The structure uses the familiar JSON format to represent the entire file as follows.

{ "archiveVersion" : "1", "classes" : { }, "objects" : { "0C3E934A20280D7E00C7CF6B" : { "fileRef" : "0C3E934920280D7E00C7CF6B", "isa" : "PBXBuildFile" }, ... . }, "objectVersion" : "46", "rootObject" : "8C7D6FB81A709259009D5B46" }Copy the code

The most important of these is the Objects field, which contains all the configuration. Here is a glimpse of how pbxproj is organized using the main.m file from Compile Sources.

Start by finding rootObject’s ID, defined in the outermost layer of the dictionary, which represents the root node of the project. Search for the ID in Objects to find its definition. Then find the Target that it contains, which in this case points to the Target ID.

Here we take the first Target and search for its ID in the same way to find its definition:Find its BuildPhase configuration item:

Find the main.m file in its definition

Find the definition:Its file definition:

As you can see, all of its resources are identified by an ID value that is unique throughout the file to organize the logic.

Xcode Project File Format Let’s Talk about Project PBxProj

2.2. Analysis of PBXPROj files

Since I am familiar with Python, I wanted to find a library for parsing with Python at the beginning, so I found mod-PBxProj. However, after reading the document, I found that the API provided was too few and I could not obtain the compiled file list and other data, so I could not use it.

Xcodeproj is a Ruby parsing library written by CocoaPods that will do the job, but that means you’ll have to write your own scripts in Ruby. The good news is that Ruby, like Python, is a scripting language that has a lot in common and is easy to learn.

The basic methods used in the script are as follows:

Project = Xcodeproj:: project. open(project_path) Including target_name_first is to get the name of the target of target_first = project. The targets. The select {| a_target | Eql?(target_name_first)} # Compile Sources phase = target.source_build_phase Libraries Phase = target.frameworks_build_phase # Obtain Copy Bundle Resources phase = target.resources_build_phaseCopy the code
2.3 Script Implementation

The basic idea is to obtain the corresponding file list through the method in 2.2, and then compare the list to find out the differences among them, and set the corresponding ignored file list to cope with the possible differences between different targets. Obtain the file path from Target:

def file_arr_for_target(target, class_obj) if class_obj == $pbx_sources_class phase = target.source_build_phase elsif class_obj == $pbx_frameworks_class  phase = target.frameworks_build_phase elsif class_obj == $pbx_resources_class phase = target.resources_build_phase else  raise "unknow recognize class" end # puts phase file_arr = Array.new phase.files.to_a.each do |pbx_build_file| begin if  pbx_build_file.file_ref.is_a?(Xcodeproj::Project::Object::PBXVariantGroup) pbx_build_file.file_ref.children.each do |item| file_arr << item.real_path.to_s end else file_arr << pbx_build_file.file_ref.real_path.to_s end rescue # Some of the values are not PBXVariantGroup or PBXFileReference, so it will fail to handle this. next end end return file_arr endCopy the code

In the actual test, two problems were encountered: 1) When taking the file path, some fileRef configurations were empty, resulting in the null value of the final path, which was also found in the source file. The reason is not clear for now, as shown in the following figure. Here, rescue is used for protection first, without further processing.

2) The value method of localized files is different from other files, because compared with other files, there is another layer, which needs to be traversed to obtain the corresponding real resource files, processing is as follows:

if pbx_build_file.file_ref.is_a? (Xcodeproj::Project::Object::PBXVariantGroup) pbx_build_file.file_ref.children.each do |item| file_arr << item.real_path.to_s endCopy the code

3) Specific resources in. Xcasset are not configured in. Pbxproj. But the images were also the focus of the examination. Read the relevant introduction, which is essentially a collection of folders. So finally through the file traversal way to check.

def get_items_arr_in_folder(folder_path) items_arr = Array.new Dir.foreach(folder_path) do |file| if file == "." or file = = ".." or file == ".DS_Store" next end path = File.join folder_path, file items_arr << path if File.directory? path items_arr += get_items_arr_in_folder path end end items_arr end def get_relative_paths_arr_in_folder(folder_path) paths_arr = get_items_arr_in_folder folder_path paths_arr.map do |path| path.slice! folder_path path end end def verify_assets(first_asset, last_asset) first_asset_list = get_relative_paths_arr_in_folder first_asset last_asset_list = get_relative_paths_arr_in_folder last_asset puts "\n--------\ncount:#{first_asset_list.length}, #{last_asset_list.length}\n--------\n" abnormal_list = first_asset_list - last_asset_list - $asset_ignore_keys reverse_abnormal_list = last_asset_list - first_asset_list - $asset_reverse_ignore_keys return abnormal_list, reverse_abnormal_list endCopy the code

Asset Catalog Format Reference

2.4 Engineering Integration

In order to facilitate timely detection of problems, these check items are integrated into the project and checked before each compilation as follows:

1) Create a new run script and rename it TargetVerify. Note: The run script is usually created at the end of the Build Phase. We want it to be executed before Compile Sources, so drag it to the front of Compile Sources. 2) Fill it with shell commands to execute ruby scripts

#! Run Phases -> Run Script echo "start verify target..." pwd declare -a cmd_list=("ruby ./script/target_verify/target_verify.rb ./xxx.xcodeproj <#target name first#> <#target name last#>" "ruby ./script/target_verify/asset_verify.rb ./xxxx/xxxx.xcassets ./xxxx/xxx.xcassets") for cmd in "${cmd_list[@]}" do eval "$cmd" if [ $? -ne 0 ] then echo "FAILED" exit 1 fi done echo "finished target verify and no issue found"Copy the code

This is configured so that at run or compile time, if the script fails to run, a compile failure is reported.

3. Github

The complete script implementation has been uploaded to Github, hoping to help you. target_verify