sbbuild 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sbuild +3 -3
  3. data/lib/sbuild/version.rb +1 -1
  4. data/lib/sbuild.rb +479 -460
  5. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8271638a15d211ff9e23c19302b4598aceedf10933fa64d1e49fea5468145f4
4
- data.tar.gz: 673cc69b077ae71652922c6cdfda09407f39e5853e396a1683ec633887261455
3
+ metadata.gz: e115a7a9bd283d13359c78e9a8124e3dfb5ba1bbe8346d9cb260a449ad86f208
4
+ data.tar.gz: 0fa0776290466973c8087cfddc1196954a7fbb4d3d1cf6de27c59e47fca89233
5
5
  SHA512:
6
- metadata.gz: 98677dea8e9f00d7816fb41adbd01c20bde5bdedceacac5d770f78eb01d1655234f5d8a08e1871c0362a3678ad312073b9a0a2aabc68472c371e12db406e450a
7
- data.tar.gz: 0cc2264ad78b3d3072c4c02c4b47afbc02f93dbe0604249e848c9474b6bf64dad928441a9f94c31cf4b43c051a0b0b25d2f484078b1bf16afa878e3ce67c006c
6
+ metadata.gz: 578c2942c7c0ed43384cebc27646421fbf0f11a07b5956fe443d6fa726ead634dd3f63ac323f03727237ebcec0e6e9210d3765f25e9eac8c6ffc7fdb14d5cca4
7
+ data.tar.gz: e9f8fe47ec2e45dcb309336ff59d093d1d70da3aaab4fe510b633d6a6184e42011caf4dafdb403a69465fd278eb5bd992305fb09f13adf2d226d724f8d106f0b
data/bin/sbuild CHANGED
@@ -7,15 +7,15 @@ manager = CacheManager.new
7
7
  if ARGV[0] == "begin"
8
8
  manager.project_task_begin(ARGV)
9
9
  elsif ARGV[0] == "finish"
10
- manager.project_task_end
10
+ manager.project_task_end(ARGV)
11
11
  elsif ARGV[0] == "cache"
12
12
  manager.add_cache_flag
13
13
  elsif ARGV[0] == "copy"
14
14
  manager.copy_cache
15
15
  elsif ARGV[0] == "target_env"
16
16
  manager.set_target_env
17
- elsif ARGV[0] == "project_env"
18
- manager.set_project_env
17
+ elsif ARGV[0] == "main_project_env"
18
+ manager.set_main_project_env
19
19
  else
20
20
  puts "<ERROR> please input prama such as (begin, finish, inject, copy) "
21
21
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sbuild
4
- VERSION = "1.0.1"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/sbuild.rb CHANGED
@@ -5,6 +5,7 @@ require 'xcodeproj'
5
5
  require 'pathname'
6
6
  require "find"
7
7
  require 'set'
8
+ require 'git'
8
9
 
9
10
  #配置
10
11
 
@@ -17,6 +18,7 @@ CACHE_STATUS_HIT = "hit"
17
18
  FILE_NAME_PRODUCT = "product.tar"
18
19
  FILE_NAME_CONTEXT = "context.yml"
19
20
  FILE_NAME_TARGET_CONTEXT = "target_context.yml"
21
+ FILE_NAME_MAIN_PROJECT_CONTEXT = "project_info.yml"
20
22
 
21
23
  #Xcode编译相关常量
22
24
  FULL_PRODUCT_NAME = "FULL_PRODUCT_NAME"
@@ -38,344 +40,344 @@ $exclude_target = []
38
40
 
39
41
  class CacheManager
40
42
 
41
- def get_branch_cache_folder
42
- return Dir.home + "/sbBuild" +"/#{$project_build_branch.gsub("/", ".")}"
43
- end
43
+ def get_branch_cache_folder
44
+ return Dir.home + "/sbBuild" +"/#{$project_build_branch.gsub("/", ".")}"
45
+ end
44
46
 
45
- def get_cache_root
46
- return Dir.home + "/sbBuild"
47
- end
47
+ def get_main_project_info_path
48
+ return Dir.home + "/sbBuild/" + "#{FILE_NAME_MAIN_PROJECT_CONTEXT}"
49
+ end
48
50
 
49
- def get_default_cache_folder
50
- return Dir.home + "/sbBuild/master"
51
- end
52
-
53
- def is_private_target(target)
54
- if target.name.start_with? "AK" or target.name.start_with? "EUR" or target.name.start_with? "A4K"
55
- return true
56
- else
57
- return false
58
- end
59
- end
51
+ def get_default_cache_folder
52
+ return Dir.home + "/sbBuild/master"
53
+ end
54
+
55
+ def is_private_target(target)
56
+ if target.name.start_with? "AK" or target.name.start_with? "EUR" or target.name.start_with? "A4K"
57
+ return true
58
+ else
59
+ return false
60
+ end
61
+ end
60
62
 
61
- def get_content_without_pwd(content)
62
- content = content.gsub("#{Dir.pwd}/", "").gsub(/#{Dir.pwd}(\W|$)/, '\1')
63
- return content
64
- end
65
-
66
- def get_file_md5(file)
67
- if $file_md5_hash.has_key? file
68
- return $file_md5_hash[file]
69
- end
70
- md5 = Digest::MD5.hexdigest(File.read(file))
71
- $file_md5_hash[file] = md5
72
- return md5
73
- end
74
-
63
+ def get_content_without_pwd(content)
64
+ content = content.gsub("#{Dir.pwd}/", "").gsub(/#{Dir.pwd}(\W|$)/, '\1')
65
+ return content
66
+ end
75
67
 
76
- def get_projects
77
- # project_path = ""
78
- # if File.exist? "#{get_cache_root}/#{project_name}.yml"
79
- # project_context = YAML.load(File.read("#{get_cache_root}/#{project_name}.yml"))
80
- # project_path = project_context[:project_path]
81
- # end
82
- # raise unless project_path.length > 0
83
- project_path = "Pods/Pods.xcodeproj"
84
- pods_project = Xcodeproj::Project.open(project_path)
85
- super_project_paths = get_super_project(pods_project)
86
- super_projects = []
87
- super_project_paths.each do | path |
88
- next if path.end_with? project_path
89
- project = Xcodeproj::Project.open(path)
90
- super_projects.push project
68
+ def get_file_md5(file)
69
+ if $file_md5_hash.has_key? file
70
+ return $file_md5_hash[file]
71
+ end
72
+ md5 = Digest::MD5.hexdigest(File.read(file))
73
+ $file_md5_hash[file] = md5
74
+ return md5
75
+ end
76
+
77
+
78
+ def get_projects(project_path)
79
+ pods_project_path = "Pods/Pods.xcodeproj"
80
+ if project_path.length > 0
81
+ pods_project_path = project_path + "/Pods/Pods.xcodeproj"
82
+ end
83
+ pods_project = Xcodeproj::Project.open(pods_project_path)
84
+ super_project_paths = get_super_project(pods_project)
85
+ super_projects = []
86
+ super_project_paths.each do | path |
87
+ next if path.end_with? pods_project_path
88
+ project = Xcodeproj::Project.open(path)
89
+ super_projects.push project
90
+ end
91
+ return (super_projects + [pods_project])
91
92
  end
92
- return (super_projects + [pods_project])
93
- end
94
93
 
95
- def get_super_project(project)
96
- wrapper_projects = project.files.select{|file|file.last_known_file_type=="wrapper.pb-project"}
97
- wrapper_project_paths = []
98
- wrapper_projects.each do | wrapper_project_file |
99
- wrapper_project_file_path = wrapper_project_file.real_path.to_s
100
- wrapper_project_paths.push wrapper_project_file_path
101
- end
102
- return wrapper_project_paths.uniq
103
- end
94
+ def get_super_project(project)
95
+ wrapper_projects = project.files.select{|file|file.last_known_file_type=="wrapper.pb-project"}
96
+ wrapper_project_paths = []
97
+ wrapper_projects.each do | wrapper_project_file |
98
+ wrapper_project_file_path = wrapper_project_file.real_path.to_s
99
+ wrapper_project_paths.push wrapper_project_file_path
100
+ end
101
+ return wrapper_project_paths.uniq
102
+ end
104
103
 
104
+
105
+ def backup_project(project)
106
+ command = "cp \"#{project.path}/project.pbxproj\" \"#{project.path}/ysTest_backup.pbxproj\""
107
+ raise unless system command
108
+ end
109
+
110
+ def clean_temp_files
111
+
112
+ command = "rm -rf Pods/*.xcodeproj/*.#{FILE_NAME_TARGET_CONTEXT}"
113
+ raise unless system command
114
+ end
105
115
 
106
- def backup_project(project)
107
- command = "cp \"#{project.path}/project.pbxproj\" \"#{project.path}/ysTest_backup.pbxproj\""
108
- raise unless system command
109
- end
110
-
111
- def clean_temp_files
112
-
113
- command = "rm -rf Pods/*.xcodeproj/*.#{FILE_NAME_TARGET_CONTEXT}"
114
- raise unless system command
115
- end
116
-
117
- def restore_project(project)
118
- if File.exist? "#{project.path}/ysTest_backup.pbxproj"
119
- command = "mv \"#{project.path}/ysTest_backup.pbxproj\" \"#{project.path}/project.pbxproj\""
120
- raise unless system command
121
- end
122
- end
123
-
124
-
125
- def can_cache_target(target)
126
- if target.product_type == "com.apple.product-type.bundle" or
127
- target.product_type == "com.apple.product-type.library.static" or
128
- target.product_type == "com.apple.product-type.framework"
129
- return true
130
- end
131
- return false
132
- end
116
+ def restore_project(project)
117
+ if File.exist? "#{project.path}/ysTest_backup.pbxproj"
118
+ command = "mv \"#{project.path}/ysTest_backup.pbxproj\" \"#{project.path}/project.pbxproj\""
119
+ raise unless system command
120
+ end
121
+ end
122
+
123
+
124
+ def can_cache_target(target)
125
+ if target.product_type == "com.apple.product-type.bundle" or
126
+ target.product_type == "com.apple.product-type.library.static" or
127
+ target.product_type == "com.apple.product-type.framework"
128
+ return true
129
+ end
130
+ return false
131
+ end
133
132
 
134
133
 
135
- def get_target_source_files(target)
136
- files = []
137
- #获取所有可执行文件(.m .swift .cpp .mm)
138
- target.source_build_phase.files.each do | file |
139
- file_path = file.file_ref.real_path.to_s
140
- files.push file_path
141
- end
134
+ def get_target_source_files(target)
135
+ files = []
136
+ #获取所有可执行文件(.m .swift .cpp .mm)
137
+ target.source_build_phase.files.each do | file |
138
+ file_path = file.file_ref.real_path.to_s
139
+ files.push file_path
140
+ end
142
141
 
143
- #获取所有头文件(.h)
144
- target.headers_build_phase.files.each do | file |
145
- file_path = file.file_ref.real_path.to_s
146
- files.push file_path
147
- end
148
- #获取所有资源文件(.png,.strings .json .html)
149
- target.resources_build_phase.files.each do | file |
150
- file_path = file.file_ref.real_path.to_s
151
- files.push file_path
152
- end
153
- expand_files = []
154
- files.uniq.each do | file |
155
- next unless File.exist? file
156
- if File.file? file
157
- expand_files.push file
158
- else
159
- Find.find(file).each do | file_in_dir |
160
- if File.file? file_in_dir
161
- expand_files.push file_in_dir
162
- end
163
- end
164
- end
165
- end
166
- return expand_files.uniq
167
- end
168
-
169
-
170
- def generate_target_all_infomation(project, target, source_files)
171
- if $podfile_spec_checksums == nil and File.exist? "#{project.path}/Podfile.lock"
172
- podfile_lock = YAML.load(File.read("#{project.path}/Podfile.lock"))
173
- $podfile_spec_checksums = podfile_lock["SPEC CHECKSUMS"]
174
- end
175
-
176
- project_configuration = project.build_configurations.detect { | config | config.name == $target_build_configuration}
177
- project_configuration_content = project_configuration.pretty_print.to_yaml
178
-
179
- project_xcconfig = ""
180
- if project_configuration.base_configuration_reference
181
- config_file_path = project_configuration.base_configuration_reference.real_path
182
- if File.exist? config_file_path
183
- project_xcconfig = File.read(config_file_path).lines.reject{|line|line.include? "_SEARCH_PATHS"}.sort.join("")
184
- end
185
- end
186
-
187
- target_configuration = target.build_configurations.detect { | config | config.name == $target_build_configuration}
188
- target_configuration_content = target_configuration.pretty_print.to_yaml
189
-
190
- target_xcconfig = ""
191
- if target_configuration.base_configuration_reference
192
- config_file_path = target_configuration.base_configuration_reference.real_path
193
- if File.exist? config_file_path
194
- target_xcconfig = File.read(config_file_path).lines.reject{|line|line.include? "_SEARCH_PATHS"}.sort.join("")
195
- end
196
- end
197
-
198
-
199
- files_configuration = []
200
- build_phases = []
201
- build_phases.push target.source_build_phase if target.methods.include? :source_build_phase
202
- build_phases.push target.resources_build_phase if target.methods.include? :resources_build_phase
203
- build_phases.each do | build_phase |
204
- target.source_build_phase.files_references.each do | files_reference |
205
- if files_reference.class == Xcodeproj::Project::Object::PBXVariantGroup
206
- files_reference.files.each do |file_ref_in_group|
207
- file_ref_in_group.build_files.each do |build_file|
208
- if build_file.settings and build_file.settings.class == Hash
209
- first_configuration.push File.basename(build_file.file_ref.real_path.to_s) + "\n" + build_file.settings.to_yaml
210
- end
211
- end
212
- end
213
- else
214
- files_reference.build_files.each do |build_file|
215
- if build_file.settings and build_file.settings.class == Hash
216
- files_configuration.push File.basename(build_file.file_ref.real_path.to_s) + "\n" + build_file.settings.to_yaml
217
- end
218
- end
219
- end
220
- end
221
- files_configuration = files_configuration.sort.uniq.join("\n")
222
-
223
-
224
- source_md5_list = []
225
- has_found_checksum = false
226
- split_parts = target.name.split("-")
227
- split_parts.each_with_index do | part, index |
228
- spec_name = split_parts[0..index].join("-")
229
- if $podfile_spec_checksums.has_key? spec_name
230
- source_md5_list.push "SPEC CHECKSUM : #{spec_name} #{$podfile_spec_checksums[spec_name]}"
231
- has_found_checksum = true
232
- end
233
- end
234
-
235
- if has_found_checksum
236
- if target.name.start_with? "AK" or target.name.start_with? "EUR"
237
-
238
- source_md5_list.push "Project : #{File.basename(project.path)}"
239
- source_md5_list.push "Project configuration : "
240
- source_md5_list.push project_configuration_content.strip
241
- source_md5_list.push "Project xcconfig : "
242
- source_md5_list.push project_xcconfig.strip
142
+ #获取所有头文件(.h)
143
+ target.headers_build_phase.files.each do | file |
144
+ file_path = file.file_ref.real_path.to_s
145
+ files.push file_path
146
+ end
147
+ #获取所有资源文件(.png,.strings .json .html)
148
+ target.resources_build_phase.files.each do | file |
149
+ file_path = file.file_ref.real_path.to_s
150
+ files.push file_path
151
+ end
152
+ expand_files = []
153
+ files.uniq.each do | file |
154
+ next unless File.exist? file
155
+ if File.file? file
156
+ expand_files.push file
157
+ else
158
+ Find.find(file).each do | file_in_dir |
159
+ if File.file? file_in_dir
160
+ expand_files.push file_in_dir
161
+ end
162
+ end
163
+ end
164
+ end
165
+ return expand_files.uniq
166
+ end
243
167
 
244
- source_md5_list.push "Target : #{target.name}, #{target.product_type}"
245
- source_md5_list.push "Target configuration : "
246
- source_md5_list.push target_configuration_content.strip
247
- source_md5_list.push "Target xcconfig : "
248
- source_md5_list.push target_xcconfig.strip
249
- source_md5_list.push "Files settings : "
250
- source_md5_list.push files_configuration.strip
251
168
 
252
- source_md5_list.push "Files MD5 : "
253
- source_files.uniq.sort.each do | file |
254
- path = file
255
- if target.name == "AKULocalizedStrings" or target.name == "EURLocalizedStrings"
256
- if file.include? ".swift"
257
- source_md5_list.push path + " : " + get_file_md5(path)
258
- end
259
- else
260
- source_md5_list.push path + " : " + get_file_md5(path)
261
- end
262
- end
263
- else
264
- source_md5_list.push "Target configuration: #{$target_build_configuration}"
265
- end
169
+ def generate_target_all_infomation(main_path, project, target, source_files)
170
+ podfile_lock_path = "Podfile.lock"
171
+ if main_path.length > 0
172
+ podfile_lock_path = main_path + "/Podfile.lock"
173
+ end
174
+ if $podfile_spec_checksums == nil and File.exist? podfile_lock_path
175
+ podfile_lock = YAML.load(File.read(podfile_lock_path))
176
+ $podfile_spec_checksums = podfile_lock["SPEC CHECKSUMS"]
177
+ end
178
+
179
+ project_configuration = project.build_configurations.detect { | config | config.name == $target_build_configuration}
180
+ project_configuration_content = project_configuration.pretty_print.to_yaml
181
+
182
+ project_xcconfig = ""
183
+ if project_configuration.base_configuration_reference
184
+ config_file_path = project_configuration.base_configuration_reference.real_path
185
+ if File.exist? config_file_path
186
+ project_xcconfig = File.read(config_file_path).lines.reject{|line|line.include? "_SEARCH_PATHS"}.sort.join("")
187
+ end
188
+ end
189
+
190
+ target_configuration = target.build_configurations.detect { | config | config.name == $target_build_configuration}
191
+ target_configuration_content = target_configuration.pretty_print.to_yaml
192
+
193
+ target_xcconfig = ""
194
+ if target_configuration.base_configuration_reference
195
+ config_file_path = target_configuration.base_configuration_reference.real_path
196
+ if File.exist? config_file_path
197
+ target_xcconfig = File.read(config_file_path).lines.reject{|line|line.include? "_SEARCH_PATHS"}.sort.join("")
198
+ end
199
+ end
200
+
201
+
202
+ files_configuration = []
203
+ build_phases = []
204
+ build_phases.push target.source_build_phase if target.methods.include? :source_build_phase
205
+ build_phases.push target.resources_build_phase if target.methods.include? :resources_build_phase
206
+ build_phases.each do | build_phase |
207
+ target.source_build_phase.files_references.each do | files_reference |
208
+ if files_reference.class == Xcodeproj::Project::Object::PBXVariantGroup
209
+ files_reference.files.each do |file_ref_in_group|
210
+ file_ref_in_group.build_files.each do |build_file|
211
+ if build_file.settings and build_file.settings.class == Hash
212
+ first_configuration.push File.basename(build_file.file_ref.real_path.to_s) + "\n" + build_file.settings.to_yaml
213
+ end
214
+ end
215
+ end
216
+ else
217
+ files_reference.build_files.each do |build_file|
218
+ if build_file.settings and build_file.settings.class == Hash
219
+ files_configuration.push File.basename(build_file.file_ref.real_path.to_s) + "\n" + build_file.settings.to_yaml
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+ files_configuration = files_configuration.sort.uniq.join("\n")
226
+
227
+
228
+ source_md5_list = []
229
+ has_found_checksum = false
230
+ split_parts = target.name.split("-")
231
+ split_parts.each_with_index do | part, index |
232
+ spec_name = split_parts[0..index].join("-")
233
+ if $podfile_spec_checksums.has_key? spec_name
234
+ source_md5_list.push "SPEC CHECKSUM : #{spec_name} #{$podfile_spec_checksums[spec_name]}"
235
+ has_found_checksum = true
236
+ end
237
+ end
238
+
239
+ if has_found_checksum
240
+ if target.name.start_with? "AK" or target.name.start_with? "EUR"
241
+
242
+ source_md5_list.push "Project : #{File.basename(project.path)}"
243
+ source_md5_list.push "Project configuration : "
244
+ source_md5_list.push project_configuration_content.strip
245
+ source_md5_list.push "Project xcconfig : "
246
+ source_md5_list.push project_xcconfig.strip
247
+
248
+ source_md5_list.push "Target : #{target.name}, #{target.product_type}"
249
+ source_md5_list.push "Target configuration : "
250
+ source_md5_list.push target_configuration_content.strip
251
+ source_md5_list.push "Target xcconfig : "
252
+ source_md5_list.push target_xcconfig.strip
253
+ source_md5_list.push "Files settings : "
254
+ source_md5_list.push files_configuration.strip
255
+
256
+ source_md5_list.push "Files MD5 : "
257
+ source_files.uniq.sort.each do | file |
258
+ path = file
259
+ if target.name == "AKULocalizedStrings" or target.name == "EURLocalizedStrings"
260
+ if file.include? ".swift"
261
+ source_md5_list.push path + " : " + get_file_md5(path)
262
+ end
263
+ else
264
+ source_md5_list.push path + " : " + get_file_md5(path)
265
+ end
266
+ end
267
+ else
268
+ source_md5_list.push "Target configuration: #{$target_build_configuration}"
269
+ end
266
270
 
267
- source_md5_content = source_md5_list.join("\n")
268
- return source_md5_content
269
-
270
- end
271
- end
271
+ source_md5_content = source_md5_list.join("\n")
272
+ return source_md5_content
273
+ end
274
+ end
272
275
 
273
276
 
274
277
 
275
- def get_target_cache(target, target_md5)
278
+ def get_target_cache(target, target_md5)
276
279
 
277
- dependency_start_time = Time.now
278
-
279
- if is_private_target(target)
280
- target_cache_dirs = Dir.glob(get_branch_cache_folder + "/" + target.name + "-" + target_md5 + "-*")
281
- else
282
- target_cache_dirs = Dir.glob(get_default_cache_folder + "/" + target.name + "-" + target_md5 + "-*")
283
- end
284
-
285
- hit_results = []
286
-
287
- target_cache_dirs.each do |target_cache_dir|
288
- unless File.exist? target_cache_dir + "/" + FILE_NAME_PRODUCT
289
- puts "<ERROR> #{target.name} target cache dir missed files: #{target_cache_dir}"
290
- next
291
- end
292
- unless File.exist? target_cache_dir + "/" + FILE_NAME_CONTEXT
293
- puts "<ERROR> #{target.name} target cache dir missed files: #{target_cache_dir}"
294
- next
295
- end
296
-
297
- target_context = YAML.load(File.read(target_cache_dir + "/" + FILE_NAME_CONTEXT))
298
-
299
- if target_context[:target_md5] != target_md5 or target_context[:product_md5] != get_file_md5(target_cache_dir + "/" + FILE_NAME_PRODUCT)
300
- command = "rm -rf \"#{target_cache_dir}\""
301
- raise unless system command
302
- puts "<ERROR> #{target.name} target md5 does not match: #{target_cache_dir}"
303
- end
304
-
305
- hit_results.push target_cache_dir
280
+ dependency_start_time = Time.now
281
+
282
+ if is_private_target(target)
283
+ target_cache_dirs = Dir.glob(get_branch_cache_folder + "/" + target.name + "-" + target_md5 + "-*")
284
+ else
285
+ target_cache_dirs = Dir.glob(get_default_cache_folder + "/" + target.name + "-" + target_md5 + "-*")
286
+ end
287
+
288
+ hit_results = []
289
+
290
+ target_cache_dirs.each do |target_cache_dir|
291
+ unless File.exist? target_cache_dir + "/" + FILE_NAME_PRODUCT
292
+ puts "<ERROR> #{target.name} target cache dir missed files: #{target_cache_dir}"
293
+ next
294
+ end
295
+ unless File.exist? target_cache_dir + "/" + FILE_NAME_CONTEXT
296
+ puts "<ERROR> #{target.name} target cache dir missed files: #{target_cache_dir}"
297
+ next
298
+ end
299
+
300
+ target_context = YAML.load(File.read(target_cache_dir + "/" + FILE_NAME_CONTEXT))
301
+
302
+ if target_context[:target_md5] != target_md5 or target_context[:product_md5] != get_file_md5(target_cache_dir + "/" + FILE_NAME_PRODUCT)
303
+ command = "rm -rf \"#{target_cache_dir}\""
304
+ raise unless system command
305
+ puts "<ERROR> #{target.name} target md5 does not match: #{target_cache_dir}"
306
+ end
307
+
308
+ hit_results.push target_cache_dir
306
309
 
307
- end
310
+ end
308
311
 
309
- return hit_results
312
+ return hit_results
310
313
 
311
- end
314
+ end
312
315
 
313
- def set_project_env
314
- project_name = ARGV[1]
315
- project_path = ARGV[2]
316
+ def set_main_project_env
317
+ main_project_name = ARGV[1]
318
+ main_project_path = ARGV[2]
316
319
 
317
- project_context = {}
318
- project_context[:project_name] = project_name
319
- project_context[:project_path] = project_path
320
- File.write("#{get_cache_root}/#{project_name}.yml", project_context.to_yaml)
321
- end
320
+ target_context = {}
321
+ if File.exist? get_main_project_info_path
322
+ target_context = YAML.load(File.read(get_main_project_info_path))
323
+ end
324
+ target_context[main_project_name] = main_project_path
325
+ File.write(get_main_project_info_path, target_context.to_yaml)
326
+ end
322
327
 
323
- def set_target_env
324
- target_name = ARGV[1]
325
- project_path = ARGV[2]
326
- branch_name = ARGV[3]
328
+ def set_target_env
329
+ target_name = ARGV[1]
330
+ project_path = ARGV[2]
327
331
 
328
- target_context = {}
329
- target_context[:branch_name] = branch_name
330
- [SYMROOT, OBJROOT, SRCROOT, CONFIGURATION_BUILD_DIR, TARGET_BUILD_DIR, TARGET_TEMP_DIR, FULL_PRODUCT_NAME].sort.each do | key |
331
- if ENV[key]
332
- target_context[key] = ENV[key]
332
+ target_context = {}
333
+ [SYMROOT, OBJROOT, SRCROOT, CONFIGURATION_BUILD_DIR, TARGET_BUILD_DIR, TARGET_TEMP_DIR, FULL_PRODUCT_NAME].sort.each do | key |
334
+ if ENV[key]
335
+ target_context[key] = ENV[key]
336
+ end
333
337
  end
338
+ target_context[:target_status] = CACHE_STATUS_READY
339
+ File.write("#{project_path}/#{target_name}.#{FILE_NAME_TARGET_CONTEXT}", target_context.to_yaml)
334
340
  end
335
- target_context[:target_status] = CACHE_STATUS_READY
336
- File.write("#{project_path}/#{target_name}.#{FILE_NAME_TARGET_CONTEXT}", target_context.to_yaml)
337
- end
338
-
339
- def add_cache_flag
340
- target_name = ARGV[1]
341
- project_path = ARGV[2]
342
- if File.exist? "#{project_path}/#{target_name}.#{FILE_NAME_TARGET_CONTEXT}"
343
- target_context = YAML.load(File.read("#{project_path}/#{target_name}.#{FILE_NAME_TARGET_CONTEXT}"))
344
- [SYMROOT, OBJROOT, SRCROOT, CONFIGURATION_BUILD_DIR, TARGET_BUILD_DIR, TARGET_TEMP_DIR, FULL_PRODUCT_NAME].sort.each do | key |
345
- if ENV[key]
346
- target_context[key] = ENV[key]
347
- end
348
- end
349
- target_context[:target_status] = CACHE_STATUS_READY
350
- File.write("#{project_path}/#{target_name}.#{FILE_NAME_TARGET_CONTEXT}", target_context.to_yaml)
351
- end
352
- end
353
-
354
- def inject_cache_action(project, target)
355
- command_exec = "\"#{$0}\""
356
- inject_phase = target.new_shell_script_build_phase("ys_cache_#{target.name}")
357
- inject_phase.shell_script = "#{command_exec} #{"cache"} #{target.name} \"#{project.path}\""
358
- inject_phase.show_env_vars_in_log = '1'
359
- end
360
-
341
+
342
+ def add_cache_flag
343
+ target_name = ARGV[1]
344
+ project_path = ARGV[2]
345
+ if File.exist? "#{project_path}/#{target_name}.#{FILE_NAME_TARGET_CONTEXT}"
346
+ target_context = YAML.load(File.read("#{project_path}/#{target_name}.#{FILE_NAME_TARGET_CONTEXT}"))
347
+ [SYMROOT, OBJROOT, SRCROOT, CONFIGURATION_BUILD_DIR, TARGET_BUILD_DIR, TARGET_TEMP_DIR, FULL_PRODUCT_NAME].sort.each do | key |
348
+ if ENV[key]
349
+ target_context[key] = ENV[key]
350
+ end
351
+ end
352
+ target_context[:target_status] = CACHE_STATUS_READY
353
+ File.write("#{project_path}/#{target_name}.#{FILE_NAME_TARGET_CONTEXT}", target_context.to_yaml)
354
+ end
355
+ end
356
+
357
+ def inject_cache_action(project, target)
358
+ command_exec = "\"#{$0}\""
359
+ inject_phase = target.new_shell_script_build_phase("sb_cache_#{target.name}")
360
+ inject_phase.shell_script = "#{command_exec} #{"cache"} #{target.name} \"#{project.path}\""
361
+ inject_phase.show_env_vars_in_log = '1'
362
+ end
363
+
361
364
  def inject_copy_action(project, target, target_context)
362
- target_cache_dir = target_context[:hit_target_cache_dir]
363
-
364
- target.build_phases.delete_if { | build_phase |
365
- build_phase.class == Xcodeproj::Project::Object::PBXHeadersBuildPhase or
366
- build_phase.class == Xcodeproj::Project::Object::PBXSourcesBuildPhase or
367
- build_phase.class == Xcodeproj::Project::Object::PBXResourcesBuildPhase
368
- }
369
-
370
- command_exec = "\"#{$0}\""
371
- inject_phase = target.new_shell_script_build_phase("ys_copy_#{target.name}")
372
- inject_phase.shell_script = "#{command_exec} #{"copy"} \"#{target_cache_dir}\""
373
- inject_phase.show_env_vars_in_log = '1'
374
- end
365
+ target_cache_dir = target_context[:hit_target_cache_dir]
366
+
367
+ target.build_phases.delete_if { | build_phase |
368
+ build_phase.class == Xcodeproj::Project::Object::PBXHeadersBuildPhase or
369
+ build_phase.class == Xcodeproj::Project::Object::PBXSourcesBuildPhase or
370
+ build_phase.class == Xcodeproj::Project::Object::PBXResourcesBuildPhase
371
+ }
372
+
373
+ command_exec = "\"#{$0}\""
374
+ inject_phase = target.new_shell_script_build_phase("sb_copy_#{target.name}")
375
+ inject_phase.shell_script = "#{command_exec} #{"copy"} \"#{target_cache_dir}\""
376
+ inject_phase.show_env_vars_in_log = '1'
377
+ end
375
378
 
376
379
  def copy_cache
377
380
 
378
- puts "<INFO> #{Time.now.to_f.to_s}"
379
381
  start_time = Time.now
380
382
  target_cache_dir = ARGV[1]
381
383
  cache_product_path = target_cache_dir + "/#{FILE_NAME_PRODUCT}"
@@ -406,182 +408,199 @@ class CacheManager
406
408
  end
407
409
 
408
410
  puts "<INFO> duration = #{((Time.now - start_time)*1000).to_i} ms in copy cache action"
409
- puts "<INFO> #{Time.now.to_f.to_s}"
411
+
410
412
  end
411
413
 
412
414
 
413
- def add_cache(target, target_info)
415
+ def add_cache(target, target_info)
414
416
 
415
- target_md5 = target_info[:target_md5]
416
- product_dir = target_info[CONFIGURATION_BUILD_DIR]
417
- full_product_name = target_info[FULL_PRODUCT_NAME]
418
- $project_build_branch = target_info[:branch_name]
417
+ target_md5 = target_info[:target_md5]
418
+ product_dir = target_info[CONFIGURATION_BUILD_DIR]
419
+ full_product_name = target_info[FULL_PRODUCT_NAME]
419
420
 
421
+ unless product_dir.length > 0 and full_product_name.length > 0
422
+ [SYMROOT, OBJROOT, SRCROOT, CONFIGURATION_BUILD_DIR, TARGET_BUILD_DIR, TARGET_TEMP_DIR, FULL_PRODUCT_NAME].sort.each do | key |
423
+ if CONFIGURATION_BUILD_DIR and ENV[CONFIGURATION_BUILD_DIR]
424
+ product_dir = ENV[CONFIGURATION_BUILD_DIR]
425
+ end
420
426
 
421
- unless product_dir.length > 0 and full_product_name.length > 0
422
- [SYMROOT, OBJROOT, SRCROOT, CONFIGURATION_BUILD_DIR, TARGET_BUILD_DIR, TARGET_TEMP_DIR, FULL_PRODUCT_NAME].sort.each do | key |
423
- if CONFIGURATION_BUILD_DIR and ENV[CONFIGURATION_BUILD_DIR]
424
- product_dir = ENV[CONFIGURATION_BUILD_DIR]
425
- end
427
+ if FULL_PRODUCT_NAME and ENV[FULL_PRODUCT_NAME]
428
+ full_product_name = ENV[FULL_PRODUCT_NAME]
429
+ end
430
+ end
431
+ end
432
+
433
+ Dir.glob("#{product_dir}/**/*.modulemap").each do | modulemap |
434
+ modulemap_content = File.read(modulemap)
435
+ if modulemap_content.include? File.dirname(modulemap) + "/"
436
+ modulemap_content = modulemap_content.gsub(File.dirname(modulemap) + "/", "")
437
+ File.write(modulemap, modulemap_content)
438
+ end
439
+ end
426
440
 
427
- if FULL_PRODUCT_NAME and ENV[FULL_PRODUCT_NAME]
428
- full_product_name = ENV[FULL_PRODUCT_NAME]
429
- end
430
- end
431
- end
432
-
433
- Dir.glob("#{product_dir}/**/*.modulemap").each do | modulemap |
434
- modulemap_content = File.read(modulemap)
435
- if modulemap_content.include? File.dirname(modulemap) + "/"
436
- modulemap_content = modulemap_content.gsub(File.dirname(modulemap) + "/", "")
437
- File.write(modulemap, modulemap_content)
438
- end
439
- end
441
+ unless full_product_name and full_product_name.size > 0 and File.exist? "#{product_dir}/#{full_product_name}"
442
+ puts "<ERROR> #{target.name} #{product_dir}/#{full_product_name} should exist"
443
+ return false
444
+ end
445
+
446
+ zip_start_time = Time.now
447
+
448
+ command = "cd \"#{File.dirname(product_dir)}\" && tar -L -c -f #{target.name}.#{FILE_NAME_PRODUCT} #{File.basename(product_dir)}/#{full_product_name}"
449
+ if target.product_type == "com.apple.product-type.library.static"
450
+ command = "cd \"#{File.dirname(product_dir)}\" && tar --exclude=*.bundle --exclude=*.framework -L -c -f #{target.name}.#{FILE_NAME_PRODUCT} #{File.basename(product_dir)}"
451
+ end
452
+
453
+ unless system command
454
+ puts "<INFO> #{command} should succeed"
455
+ return false
456
+ end
440
457
 
441
- unless full_product_name and full_product_name.size > 0 and File.exist? "#{product_dir}/#{full_product_name}"
442
- puts "<ERROR> #{target.name} #{product_dir}/#{full_product_name} should exist"
443
- return false
444
- end
445
-
446
- zip_start_time = Time.now
447
-
448
- command = "cd \"#{File.dirname(product_dir)}\" && tar -L -c -f #{target.name}.#{FILE_NAME_PRODUCT} #{File.basename(product_dir)}/#{full_product_name}"
449
- if target.product_type == "com.apple.product-type.library.static"
450
- command = "cd \"#{File.dirname(product_dir)}\" && tar --exclude=*.bundle --exclude=*.framework -L -c -f #{target.name}.#{FILE_NAME_PRODUCT} #{File.basename(product_dir)}"
451
- end
452
-
453
- unless system command
454
- puts "<INFO> #{command} should succeed"
455
- return false
456
- end
457
458
 
459
+ if is_private_target(target)
460
+ already_target_cache_dirs = Dir.glob(get_branch_cache_folder + "/" + target.name + "-" + target_md5 + "-*")
461
+ else
462
+ already_target_cache_dirs = Dir.glob(get_default_cache_folder + "/" + target.name + "-" + target_md5 + "-*")
463
+ end
458
464
 
459
- if is_private_target(target)
460
- already_target_cache_dirs = Dir.glob(get_branch_cache_folder + "/" + target.name + "-" + target_md5 + "-*")
461
- else
462
- already_target_cache_dirs = Dir.glob(get_default_cache_folder + "/" + target.name + "-" + target_md5 + "-*")
463
- end
465
+ already_target_cache_dirs.each do |path|
466
+ if File.exist? path
467
+ raise unless system "rm -rf \"#{path}\""
468
+ end
469
+ end
464
470
 
465
- already_target_cache_dirs.each do |path|
466
- if File.exist? path
467
- raise unless system "rm -rf \"#{path}\""
468
- end
469
- end
470
471
 
472
+ if is_private_target(target)
473
+ target_cache_dir = get_branch_cache_folder + "/" + target.name + "-" + target_md5 + "-" + (Time.now.to_f * 1000).to_i.to_s
474
+ else
475
+ target_cache_dir = get_default_cache_folder + "/" + target.name + "-" + target_md5 + "-" + (Time.now.to_f * 1000).to_i.to_s
476
+ end
477
+
478
+ if File.exist? target_cache_dir
479
+ puts "<INFO> #{target_cache_dir} should not exist"
480
+ raise unless system "rm -rf \"#{target_cache_dir}\""
481
+ return false
482
+ end
483
+
484
+ command = "mkdir -p \"#{target_cache_dir}\""
485
+ unless system command
486
+ puts "<INFO> #{command} should succeed"
487
+ return false
488
+ end
489
+
490
+ cache_product_path = target_cache_dir + "/#{FILE_NAME_PRODUCT}"
491
+ command = "mv \"#{File.dirname(product_dir)}/#{target.name}.#{FILE_NAME_PRODUCT}\" \"#{cache_product_path}\""
492
+ unless system command
493
+ puts "<INFO> #{command} should succeed"
494
+ return false
495
+ end
496
+ unless File.exist? cache_product_path
497
+ puts "<INFO> #{cache_product_path} should exist after mv"
498
+ return false
499
+ end
500
+
501
+ target_info[:product_md5] = get_file_md5(cache_product_path)
502
+ target_info[:build_product_dir] = target_info[CONFIGURATION_BUILD_DIR].gsub(target_info[SYMROOT] + "/", "")
503
+ target_info[:build_intermediate_dir] = target_info[TARGET_TEMP_DIR].gsub(target_info[OBJROOT] + "/", "")
471
504
 
472
- if is_private_target(target)
473
- target_cache_dir = get_branch_cache_folder + "/" + target.name + "-" + target_md5 + "-" + (Time.now.to_f * 1000).to_i.to_s
474
- else
475
- target_cache_dir = get_default_cache_folder + "/" + target.name + "-" + target_md5 + "-" + (Time.now.to_f * 1000).to_i.to_s
476
- end
505
+ target_info.delete(:target_status)
506
+ target_info.delete(:hit_target_cache_dir)
507
+ target_info.delete(:target_md5_content)
477
508
 
478
- if File.exist? target_cache_dir
479
- puts "<INFO> #{target_cache_dir} should not exist"
480
- raise unless system "rm -rf \"#{target_cache_dir}\""
481
- return false
482
- end
509
+ File.write(target_cache_dir + "/" + FILE_NAME_CONTEXT, target_info.to_yaml)
510
+
511
+ return true
483
512
 
484
- command = "mkdir -p \"#{target_cache_dir}\""
485
- unless system command
486
- puts "<INFO> #{command} should succeed"
487
- return false
488
- end
513
+ end
489
514
 
490
- cache_product_path = target_cache_dir + "/#{FILE_NAME_PRODUCT}"
491
- command = "mv \"#{File.dirname(product_dir)}/#{target.name}.#{FILE_NAME_PRODUCT}\" \"#{cache_product_path}\""
492
- unless system command
493
- puts "<INFO> #{command} should succeed"
494
- return false
495
- end
496
- unless File.exist? cache_product_path
497
- puts "<INFO> #{cache_product_path} should exist after mv"
498
- return false
499
- end
500
515
 
501
- target_info[:product_md5] = get_file_md5(cache_product_path)
502
- target_info[:build_product_dir] = target_info[CONFIGURATION_BUILD_DIR].gsub(target_info[SYMROOT] + "/", "")
503
- target_info[:build_intermediate_dir] = target_info[TARGET_TEMP_DIR].gsub(target_info[OBJROOT] + "/", "")
516
+ def project_task_begin(argv)
504
517
 
505
- target_info.delete(:target_status)
506
- target_info.delete(:hit_target_cache_dir)
507
- target_info.delete(:target_md5_content)
518
+ system "pod install"
508
519
 
509
- File.write(target_cache_dir + "/" + FILE_NAME_CONTEXT, target_info.to_yaml)
510
-
511
- return true
512
-
513
- end
514
-
515
-
516
- def project_task_begin(argv)
520
+ g = Git.open("./")
521
+ $project_build_branch = g.current_branch
517
522
 
518
- projects = get_projects
519
- total_count = 0
520
- hit_count = 0
521
- miss_count = 0
522
- error_count = 0
523
- hit_target_md5_cache_set = Set.new
524
- miss_target_cache_set = Set.new
523
+ projects = get_projects("")
524
+ total_count = 0
525
+ hit_count = 0
526
+ miss_count = 0
527
+ error_count = 0
528
+ hit_target_md5_cache_set = Set.new
529
+ miss_target_cache_set = Set.new
525
530
 
526
- pre_targets_info = {}
531
+ pre_targets_info = {}
527
532
 
528
- projects.each do |project|
529
- # restore_project(project)
530
- backup_project(project)
531
- end
533
+ projects.each do |project|
534
+ backup_project(project)
535
+ end
532
536
 
533
- clean_temp_files
534
-
535
- # projects = get_projects
536
-
537
- projects.each do |project|
538
- project.native_targets.each do |target|
539
- if can_cache_target(target)
540
- total_count = total_count + 1
541
- source_files = get_target_source_files(target)
542
- target_md5_content = generate_target_all_infomation(project, target, source_files)
543
- unless target_md5_content
544
- puts "<ERROR> target md5 content can not generate: #{target.name}"
545
- error_count = error_count + 1
546
- next
547
- end
548
-
549
- target_md5 = Digest::MD5.hexdigest(target_md5_content)
550
- hit_target_cache_dirs = get_target_cache(target, target_md5)
551
- target_info = {}
552
-
553
- if hit_target_cache_dirs.count == 0
554
- puts "<INFO> #{target.name} #{target_md5} does not hit any cache"
555
- target_info[:target_status] = CACHE_STATUS_MISS
556
- inject_cache_action(project, target)
557
- miss_count = miss_count + 1
558
- else
559
- target_info[:hit_target_cache_dir] = hit_target_cache_dirs
560
- hit_target_md5_cache_set.add "#{target.name}-#{target_md5}"
561
-
562
- if hit_target_cache_dirs.count > 0
563
- puts "<INFO> #{target.name} #{target_md5} hit cache"
564
- hit_count = hit_count + 1
565
- target_really_hit_dir = hit_target_cache_dirs[0]
566
- hit_target_info = YAML.load(File.read(target_really_hit_dir + "/" + FILE_NAME_CONTEXT))
567
- target_info = target_info.merge!(hit_target_info)
568
- target_info[:target_status] = CACHE_STATUS_HIT
569
- target_info[:hit_target_cache_dir] = target_really_hit_dir
570
- inject_copy_action(project, target, target_info)
571
- end
572
- end
573
- File.write("#{project.path}/#{target.name}.#{FILE_NAME_TARGET_CONTEXT}", target_info.to_yaml)
574
- end
575
- end
576
- project.save
577
- end
578
- puts "<INFO> total count: #{total_count}, hit count: #{hit_count}, miss_count: #{miss_count}, error_count: #{error_count}"
579
- end
580
-
537
+ clean_temp_files
538
+
539
+ projects.each do |project|
540
+ project.native_targets.each do |target|
541
+ target.build_phases.delete_if { |phase|
542
+ phase.class == Xcodeproj::Project::Object::PBXShellScriptBuildPhase and phase.name.include? "sb_inject"
543
+ }
544
+ if can_cache_target(target)
545
+ total_count = total_count + 1
546
+ source_files = get_target_source_files(target)
547
+ target_md5_content = generate_target_all_infomation("", project, target, source_files)
548
+ unless target_md5_content
549
+ puts "<ERROR> target md5 content can not generate: #{target.name}"
550
+ error_count = error_count + 1
551
+ next
552
+ end
553
+
554
+ target_md5 = Digest::MD5.hexdigest(target_md5_content)
555
+ hit_target_cache_dirs = get_target_cache(target, target_md5)
556
+ target_info = {}
557
+
558
+ if hit_target_cache_dirs.count == 0
559
+ puts "<INFO> #{target.name} #{target_md5} does not hit any cache"
560
+ target_info[:target_status] = CACHE_STATUS_MISS
561
+ inject_cache_action(project, target)
562
+ miss_count = miss_count + 1
563
+ else
564
+ target_info[:hit_target_cache_dir] = hit_target_cache_dirs
565
+ hit_target_md5_cache_set.add "#{target.name}-#{target_md5}"
566
+
567
+ if hit_target_cache_dirs.count > 0
568
+ puts "<INFO> #{target.name} #{target_md5} hit cache"
569
+ hit_count = hit_count + 1
570
+ target_really_hit_dir = hit_target_cache_dirs[0]
571
+ hit_target_info = YAML.load(File.read(target_really_hit_dir + "/" + FILE_NAME_CONTEXT))
572
+ target_info = target_info.merge!(hit_target_info)
573
+ target_info[:target_status] = CACHE_STATUS_HIT
574
+ target_info[:hit_target_cache_dir] = target_really_hit_dir
575
+ inject_copy_action(project, target, target_info)
576
+ end
577
+ end
578
+ File.write("#{project.path}/#{target.name}.#{FILE_NAME_TARGET_CONTEXT}", target_info.to_yaml)
579
+ end
580
+ end
581
+ project.save
582
+ end
583
+ puts "<INFO> total count: #{total_count}, hit count: #{hit_count}, miss_count: #{miss_count}, error_count: #{error_count}"
584
+ end
581
585
 
582
- def project_task_end
583
586
 
584
- projects = get_projects
587
+ def project_task_end(argv)
588
+ project_name = argv[1]
589
+ project_path = ""
590
+ project_path_key = "SBUILD_#{project_name}_PATH"
591
+ project_path = ENV[project_path_key]
592
+ if project_path == nil or project_path.length == 0
593
+ if File.exist? get_main_project_info_path
594
+ project_info = YAML.load(File.read(get_main_project_info_path))
595
+ project_path = project_info[project_name]
596
+ end
597
+ end
598
+ raise unless project_path.length > 0
599
+
600
+ g = Git.open(project_path)
601
+ $project_build_branch = g.current_branch
602
+
603
+ projects = get_projects(project_path)
585
604
  post_targets_context = {}
586
605
  total_add_count = 0
587
606
 
@@ -595,7 +614,7 @@ class CacheManager
595
614
  next unless post_targets_context[:target_status] != CACHE_STATUS_HIT
596
615
 
597
616
  source_files = get_target_source_files(target)
598
- target_md5_content = generate_target_all_infomation(project, target, source_files)
617
+ target_md5_content = generate_target_all_infomation(project_path, project, target, source_files)
599
618
  next unless target_md5_content && target_md5_content.length > 0
600
619
 
601
620
  cur_target_md5 = Digest::MD5.hexdigest(target_md5_content)
@@ -606,7 +625,7 @@ class CacheManager
606
625
  puts "<INFO> #{target} is being added to cache dir"
607
626
  total_add_count = total_add_count + 1
608
627
  target.build_phases.delete_if { |phase|
609
- phase.class == Xcodeproj::Project::Object::PBXShellScriptBuildPhase and phase.name.include? "ys_"
628
+ phase.class == Xcodeproj::Project::Object::PBXShellScriptBuildPhase and phase.name.include? "sb_"
610
629
  }
611
630
  end
612
631
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sbbuild
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yusheng
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-30 00:00:00.000000000 Z
11
+ date: 2022-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: xcodeproj