sbbuild 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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