cocoapods-xccache 0.0.1

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.
@@ -0,0 +1,1047 @@
1
+ require 'xcodeproj'
2
+ require 'digest'
3
+ require 'set'
4
+ require 'open3'
5
+ require "find"
6
+ require 'yaml'
7
+ require 'pathname'
8
+
9
+ module Zabel
10
+ class Error < StandardError; end
11
+
12
+ BUILD_KEY_SYMROOT = "SYMROOT"
13
+ BUILD_KEY_CONFIGURATION_BUILD_DIR = "CONFIGURATION_BUILD_DIR"
14
+ BUILD_KEY_TARGET_BUILD_DIR = "TARGET_BUILD_DIR"
15
+ BUILD_KEY_OBJROOT = "OBJROOT"
16
+ BUILD_KEY_TARGET_TEMP_DIR = "TARGET_TEMP_DIR"
17
+ BUILD_KEY_PODS_XCFRAMEWORKS_BUILD_DIR = "PODS_XCFRAMEWORKS_BUILD_DIR"
18
+ BUILD_KEY_MODULEMAP_FILE = "MODULEMAP_FILE"
19
+ BUILD_KEY_SRCROOT = "SRCROOT"
20
+ BUILD_KEY_FULL_PRODUCT_NAME = "FULL_PRODUCT_NAME"
21
+
22
+ STATUS_HIT = "hit"
23
+ STATUS_MISS = "miss"
24
+ STATUS_MISS_AND_READY = "miss_ready"
25
+
26
+ STAGE_CLEAN = "clean"
27
+ STAGE_EXTRACT = "extract"
28
+ STAGE_PRINTENV = "printenv"
29
+ STAGE_PRE = "pre"
30
+ STAGE_POST = "post"
31
+
32
+ FILE_NAME_MESSAGE = "message.txt"
33
+ FILE_NAME_CONTEXT = "context.yml"
34
+ FILE_NAME_PRODUCT = "product.tar"
35
+ FILE_NAME_TARGET_CONTEXT = "zabel_target_context.yml"
36
+
37
+ #FILE_NAME_CACHE = "pod_cache.txt"
38
+ #PRE_SHELL_NAME = "xccache_pre"
39
+ MAIN_PROJECT_SHELL_NAME = "zable_xccache"
40
+
41
+ def self.zabel_get_cache_root
42
+ cache_root = ENV["ZABEL_CACHE_ROOT"]
43
+ if cache_root and cache_root.size > 0
44
+ return cache_root
45
+ end
46
+
47
+ return Dir.home + "/xccache"
48
+ end
49
+
50
+ def self.zabel_get_cache_count
51
+ cache_count = ENV["ZABEL_CACHE_COUNT"]
52
+ if cache_count and cache_count.to_i.to_s == cache_count
53
+ return cache_count.to_i
54
+ end
55
+ return 10000
56
+ end
57
+
58
+ def self.zabel_should_not_detect_module_map_dependency
59
+ # By default, zabel detects module map dependency.
60
+ # However, there are bugs of xcodebuild or swift-frontend, which emits unnecessary and incorrect modulemap dependencies.
61
+ # To test by run "ruby test/one.rb test/todo/modulemap_file/Podfile"
62
+ # To avoid by set "export ZABEL_NOT_DETECT_MODULE_MAP_DEPENDENCY=YES"
63
+ zabel_should_not_detect_module_map_dependency = ENV["ZABEL_NOT_DETECT_MODULE_MAP_DEPENDENCY"]
64
+ if zabel_should_not_detect_module_map_dependency == "YES"
65
+ return true
66
+ end
67
+ return false
68
+ end
69
+
70
+ def self.zabel_get_min_source_file_count
71
+ # By default, zable caches targets which count of source files is greater than or equal 1.
72
+ # You can set this value to 0 or more than 1 to achieve higher speed.
73
+ min_source_file_count = ENV["ZABEL_MIN_SOURCE_FILE_COUNT"]
74
+ if min_source_file_count and min_source_file_count.to_i.to_s == min_source_file_count
75
+ return min_source_file_count.to_i
76
+ end
77
+ return 1
78
+ end
79
+
80
+ def self.zabel_get_projects
81
+ # TODO: to support more project, not only Pods
82
+ pods_project = Xcodeproj::Project.open("Pods/Pods.xcodeproj")
83
+ wrapper_project_paths = zabel_get_wrapper_project_paths(pods_project)
84
+ wrapper_projects = []
85
+ wrapper_project_paths.each do | path |
86
+ next if path.end_with? "Pods/Pods.xcodeproj"
87
+ project = Xcodeproj::Project.open(path)
88
+ wrapper_projects.push project
89
+ end
90
+ return (wrapper_projects + [pods_project])
91
+ end
92
+
93
+ def self.zabel_get_wrapper_project_paths(project)
94
+ wrapper_projects = project.files.select{|file| file.last_known_file_type=="wrapper.pb-project"}
95
+ wrapper_project_paths = []
96
+ wrapper_projects.each do | wrapper_project_file |
97
+ wrapper_project_file_path = wrapper_project_file.real_path.to_s
98
+ wrapper_project_paths.push wrapper_project_file_path
99
+ end
100
+ return wrapper_project_paths.uniq
101
+ end
102
+
103
+ # 是否缓存
104
+ # $cache_hash = {}
105
+ # $has_load_cache_file = false
106
+
107
+ def self.zabel_can_cache_target(target)
108
+ if target.name.start_with? "Pods-"
109
+ puts "[XCCACHE/I] skip #{target.name}"
110
+ return false
111
+ end
112
+ if target.class == Xcodeproj::Project::Object::PBXNativeTarget
113
+ # see https://github.com/CocoaPods/Xcodeproj/blob/master/lib/xcodeproj/constants.rb#L145
114
+ if target.product_type == "com.apple.product-type.bundle" or
115
+ target.product_type == "com.apple.product-type.library.static" or
116
+ target.product_type == "com.apple.product-type.framework"
117
+ return true
118
+ else
119
+ puts "[XCCACHE/I] skip #{target.name} #{target.class} #{target.product_type}"
120
+ end
121
+ else
122
+ puts "[XCCACHE/I] skip #{target.name} #{target.class}"
123
+ end
124
+ return false
125
+ end
126
+
127
+ def self.zabel_get_dependency_files(target, intermediate_dir, product_dir, xcframeworks_build_dir)
128
+ dependency_files = []
129
+ Dir.glob("#{intermediate_dir}/**/*.d").each do | dependency_file |
130
+ content = File.read(dependency_file)
131
+ # see https://github.com/ccache/ccache/blob/master/src/Depfile.cpp#L141
132
+ # and this is a simple regex parser enough to get all files, as far as I know.
133
+ files = content.scan(/(?:\S(?:\\ )*)+/).flatten.uniq
134
+ files = files - ["dependencies:", "\\", ":"]
135
+
136
+ files.each do | file |
137
+ file = file.gsub("\\ ", " ")
138
+
139
+ unless File.exist? file
140
+ puts "[XCCACHE/E] #{target.name} #{file} should exist in dependency file #{dependency_file} in #{intermediate_dir}/**/*.d"
141
+ return []
142
+ end
143
+
144
+ if file.start_with? intermediate_dir + "/" or
145
+ file.start_with? product_dir + "/"
146
+ next
147
+ end
148
+
149
+ if xcframeworks_build_dir and xcframeworks_build_dir.size > 0 and file.start_with? xcframeworks_build_dir + "/"
150
+ next
151
+ end
152
+
153
+ dependency_files.push file
154
+ end
155
+ end
156
+ return dependency_files.uniq
157
+ end
158
+
159
+ def self.zabel_get_target_source_files(target)
160
+ files = []
161
+ target.source_build_phase.files.each do | file |
162
+ file_path = file.file_ref.real_path.to_s
163
+ files.push file_path
164
+ end
165
+ target.headers_build_phase.files.each do | file |
166
+ file_path = file.file_ref.real_path.to_s
167
+ files.push file_path
168
+ end
169
+ target.resources_build_phase.files.each do | file |
170
+ file_path = file.file_ref.real_path.to_s
171
+ files.push file_path
172
+ end
173
+
174
+ expand_files = []
175
+ files.uniq.each do | file |
176
+ next unless File.exist? file
177
+ if File.file? file
178
+ expand_files.push file
179
+ else
180
+ Find.find(file).each do | file_in_dir |
181
+ if File.file? file_in_dir
182
+ expand_files.push file_in_dir
183
+ end
184
+ end
185
+ end
186
+ end
187
+ return expand_files.uniq
188
+ end
189
+
190
+ def self.zabel_get_content_without_pwd(content)
191
+ content = content.gsub("#{Dir.pwd}/", "").gsub(/#{Dir.pwd}(\W|$)/, '\1')
192
+ return content
193
+ end
194
+
195
+ $zabel_file_md5_hash = {}
196
+
197
+ def self.zabel_get_file_md5(file)
198
+ if $zabel_file_md5_hash.has_key? file
199
+ return $zabel_file_md5_hash[file]
200
+ end
201
+ md5 = Digest::MD5.hexdigest(File.read(file))
202
+ $zabel_file_md5_hash[file] = md5
203
+ return md5
204
+ end
205
+
206
+ def self.zabel_keep
207
+ file_list = Dir.glob("#{zabel_get_cache_root}/*")
208
+ file_time_hash = {}
209
+ file_list.each do | file |
210
+ file_time_hash[file] = File.mtime(file)
211
+ end
212
+ file_list = file_list.sort_by {|file| - file_time_hash[file].to_f}
213
+ puts "[XCCACHE/I] keep cache " + file_list.size.to_s + " " + Open3.capture3("du -sh #{zabel_get_cache_root}")[0].to_s
214
+
215
+ if file_list.size > 1
216
+ puts "[XCCACHE/I] keep oldest " + file_time_hash[file_list.last].to_s + " " + file_list.last
217
+ puts "[XCCACHE/I] keep newest " + file_time_hash[file_list.first].to_s + " " + file_list.first
218
+ end
219
+
220
+ if file_list.size > zabel_get_cache_count
221
+ file_list_remove = file_list[zabel_get_cache_count..(file_list.size-1)]
222
+ file_list_remove.each do | file |
223
+ raise unless system "rm -rf \"#{file}\""
224
+ end
225
+ end
226
+ end
227
+
228
+ def self.zabel_clean_backup_project(project)
229
+ command = "rm -rf \"#{project.path}/project.zabel_backup_pbxproj\""
230
+ raise unless system command
231
+ end
232
+
233
+
234
+ def self.zabel_backup_project(project)
235
+ command = "cp \"#{project.path}/project.pbxproj\" \"#{project.path}/project.zabel_backup_pbxproj\""
236
+ raise unless system command
237
+ end
238
+
239
+ def self.zabel_restore_project(project)
240
+ if File.exist? "#{project.path}/project.zabel_backup_pbxproj"
241
+ command = "mv \"#{project.path}/project.zabel_backup_pbxproj\" \"#{project.path}/project.pbxproj\""
242
+ raise unless system command
243
+ end
244
+ end
245
+
246
+ $zabel_podfile_spec_checksums = nil
247
+
248
+ def self.zabel_get_target_md5_content(project, target, configuration_name, argv, source_files)
249
+
250
+ unless $zabel_podfile_spec_checksums
251
+ if File.exist? "Podfile.lock"
252
+ podfile_lock = YAML.load(File.read("Podfile.lock"))
253
+ $zabel_podfile_spec_checksums = podfile_lock["SPEC CHECKSUMS"]
254
+ end
255
+ end
256
+
257
+ project_configuration = project.build_configurations.detect { | config |
258
+ config.name == configuration_name
259
+ }
260
+ project_configuration_content = project_configuration.pretty_print.to_yaml
261
+ project_xcconfig = ""
262
+ if project_configuration.base_configuration_reference
263
+ config_file_path = project_configuration.base_configuration_reference.real_path.to_s
264
+ if File.exist? config_file_path
265
+ project_xcconfig = File.read(config_file_path).lines.reject{|line|line.include? "_SEARCH_PATHS"}.sort.join("")
266
+ end
267
+ end
268
+
269
+ target_configuration = target.build_configurations.detect { | config | config.name == configuration_name}
270
+ target_configuration_content = target_configuration.pretty_print.to_yaml
271
+ target_xcconfig = ""
272
+ if target_configuration.base_configuration_reference
273
+ config_file_path = target_configuration.base_configuration_reference.real_path.to_s
274
+ if File.exist? config_file_path
275
+ target_xcconfig = File.read(config_file_path).lines.reject{|line|line.include? "_SEARCH_PATHS"}.sort.join("")
276
+ end
277
+ end
278
+
279
+ first_configuration = []
280
+ build_phases = []
281
+ build_phases.push target.source_build_phase if target.methods.include? :source_build_phase
282
+ build_phases.push target.resources_build_phase if target.methods.include? :resources_build_phase
283
+ build_phases.each do | build_phase |
284
+ build_phase.files_references.each do | files_reference |
285
+ if files_reference.class == Xcodeproj::Project::Object::PBXVariantGroup
286
+ files_reference.files.each do |file_ref_in_group|
287
+ file_ref_in_group.build_files.each do |build_file|
288
+ if build_file.settings and build_file.settings.class == Hash
289
+ first_configuration.push File.basename(build_file.file_ref.real_path.to_s) + "\n" + build_file.settings.to_yaml
290
+ end
291
+ end
292
+ end
293
+ else
294
+ files_reference.build_files.each do |build_file|
295
+ if build_file.settings and build_file.settings.class == Hash
296
+ first_configuration.push File.basename(build_file.file_ref.real_path.to_s) + "\n" + build_file.settings.to_yaml
297
+ end
298
+ end
299
+ end
300
+ end
301
+ end
302
+ first_configuration_content = first_configuration.sort.uniq.join("\n")
303
+
304
+ key_argv = []
305
+
306
+ # TODO: to add more and test more
307
+ # However, you can control your cache keys manually by using pre and post.
308
+ temp_path_list = ["-derivedDataPath", "-archivePath", "--derived_data_path", "--archive_path", "--build_path"]
309
+ argv.each_with_index do | arg, index |
310
+ next if temp_path_list.include? arg
311
+ next if index > 0 and temp_path_list.include? argv[index-1]
312
+ next if arg.start_with? "DSTROOT="
313
+ next if arg.start_with? "OBJROOT="
314
+ next if arg.start_with? "SYMROOT="
315
+ key_argv.push arg
316
+ end
317
+
318
+ source_md5_list = []
319
+ # zabel built-in verison, which will be changed for incompatibility in the future
320
+ source_md5_list.push "Zabel cache version : #{CocoapodsXccache::CACHE_VERSION}"
321
+ source_md5_list.push "ARGV : #{key_argv.to_s}"
322
+
323
+ # TODO: to get a explicit spec name from a target.
324
+ target_possible_spec_names = []
325
+ target_possible_spec_names.push target_configuration.build_settings["PRODUCT_NAME"] if target_configuration.build_settings["PRODUCT_NAME"]
326
+ target_possible_spec_names.push target_configuration.build_settings["IBSC_MODULE"] if target_configuration.build_settings["IBSC_MODULE"]
327
+ target_possible_spec_names.push File.basename(target_configuration.build_settings["CONFIGURATION_BUILD_DIR"]) if target_configuration.build_settings["CONFIGURATION_BUILD_DIR"]
328
+ if target_xcconfig.lines.detect { | line | line.start_with? "CONFIGURATION_BUILD_DIR = "}
329
+ target_possible_spec_names.push File.basename(target_xcconfig.lines.detect { | line | line.start_with? "CONFIGURATION_BUILD_DIR = "}.strip)
330
+ end
331
+ if target_xcconfig.lines.detect { | line | line.start_with? "PODS_TARGET_SRCROOT = "}
332
+ target_possible_spec_names.push File.basename(target_xcconfig.lines.detect { | line | line.start_with? "PODS_TARGET_SRCROOT = "}.strip)
333
+ end
334
+
335
+ target_match_spec_names = []
336
+ target_possible_spec_names.uniq.sort.each do | spec_name |
337
+ if spec_name.size > 0 and $zabel_podfile_spec_checksums.has_key? spec_name
338
+ source_md5_list.push "SPEC CHECKSUM : #{spec_name} #{$zabel_podfile_spec_checksums[spec_name]}"
339
+ target_match_spec_names.push spec_name
340
+ end
341
+ end
342
+
343
+ unless target_match_spec_names.size == 1
344
+ puts "[XCCACHE/E] #{target.name} #{target_possible_spec_names.to_s} #{target_match_spec_names.to_s} SPEC CHECKSUM should be found"
345
+ puts target_configuration.build_settings.to_s
346
+ puts target_xcconfig
347
+ end
348
+
349
+ source_md5_list.push "Project : #{File.basename(project.path)}"
350
+ source_md5_list.push "Project configuration : "
351
+ source_md5_list.push project_configuration_content.strip
352
+ source_md5_list.push "Project xcconfig : "
353
+ source_md5_list.push project_xcconfig.strip
354
+ source_md5_list.push "Target : #{target.name}"
355
+ source_md5_list.push "Target type : #{target.product_type}"
356
+ source_md5_list.push "Target configuration : "
357
+ source_md5_list.push target_configuration_content.strip
358
+ source_md5_list.push "Target xcconfig : "
359
+ source_md5_list.push target_xcconfig.strip
360
+ source_md5_list.push "Files settings : "
361
+ source_md5_list.push first_configuration_content.strip
362
+
363
+ source_md5_list.push "Files MD5 : "
364
+ source_files.uniq.sort.each do | file |
365
+ source_md5_list.push zabel_get_content_without_pwd(file) + " : " + zabel_get_file_md5(file)
366
+ end
367
+
368
+ source_md5_content = source_md5_list.join("\n")
369
+ return source_md5_content
370
+ end
371
+
372
+ def self.zabel_clean_temp_files
373
+ command = "rm -rf Pods/*.xcodeproj/project.zabel_backup_pbxproj"
374
+ puts command
375
+ raise unless system command
376
+
377
+ command = "rm -rf Pods/*.xcodeproj/*.#{FILE_NAME_TARGET_CONTEXT}"
378
+ puts command
379
+ raise unless system command
380
+ end
381
+
382
+ def self.zabel_add_cache(target, target_context, message)
383
+ target_md5 = target_context[:target_md5]
384
+
385
+ product_dir = target_context[BUILD_KEY_CONFIGURATION_BUILD_DIR]
386
+ intermediate_dir = target_context[BUILD_KEY_TARGET_TEMP_DIR]
387
+ full_product_name = target_context[BUILD_KEY_FULL_PRODUCT_NAME]
388
+
389
+ target_cache_dir = zabel_get_cache_root + "/" + target.name + "-" + target_md5 + "-" + (Time.now.to_f * 1000).to_i.to_s
390
+
391
+ Dir.glob("#{product_dir}/**/*.modulemap").each do | modulemap |
392
+ modulemap_content = File.read(modulemap)
393
+ if modulemap_content.include? File.dirname(modulemap) + "/"
394
+ modulemap_content = modulemap_content.gsub(File.dirname(modulemap) + "/", "")
395
+ File.write(modulemap, modulemap_content)
396
+ end
397
+ end
398
+
399
+ unless full_product_name and full_product_name.size > 0 and File.exist? "#{product_dir}/#{full_product_name}"
400
+ puts "[XCCACHE/E] #{target.name} #{product_dir}/#{full_product_name} should exist"
401
+ return false
402
+ end
403
+
404
+ zip_start_time = Time.now
405
+
406
+ command = "cd \"#{File.dirname(product_dir)}\" && tar -L -c -f #{target.name}.#{FILE_NAME_PRODUCT} #{File.basename(product_dir)}/#{full_product_name}"
407
+ if target.product_type == "com.apple.product-type.library.static"
408
+ command = "cd \"#{File.dirname(product_dir)}\" && tar --exclude=*.bundle --exclude=*.framework -L -c -f #{target.name}.#{FILE_NAME_PRODUCT} #{File.basename(product_dir)}"
409
+ end
410
+
411
+ puts command
412
+ unless system command
413
+ puts "[XCCACHE/E] #{command} should succeed"
414
+ return false
415
+ end
416
+
417
+ if File.exist? target_cache_dir
418
+ puts "[XCCACHE/E] #{target_cache_dir} should not exist"
419
+ raise unless system "rm -rf \"#{target_cache_dir}\""
420
+ return false
421
+ end
422
+
423
+ command = "mkdir -p \"#{target_cache_dir}\""
424
+ unless system command
425
+ puts command
426
+ puts "[XCCACHE/E] #{command} should succeed"
427
+ return false
428
+ end
429
+
430
+ cache_product_path = target_cache_dir + "/#{FILE_NAME_PRODUCT}"
431
+
432
+ command = "mv \"#{File.dirname(product_dir)}/#{target.name}.#{FILE_NAME_PRODUCT}\" \"#{cache_product_path}\""
433
+ puts command
434
+ unless system command
435
+ puts command
436
+ puts "[XCCACHE/E] #{command} should succeed"
437
+ return false
438
+ end
439
+ unless File.exist? cache_product_path
440
+ puts "[XCCACHE/E] #{cache_product_path} should exist after mv"
441
+ return false
442
+ end
443
+
444
+ target_context[:product_md5] = zabel_get_file_md5(cache_product_path)
445
+ target_context[:build_product_dir] = target_context[BUILD_KEY_CONFIGURATION_BUILD_DIR].gsub(target_context[BUILD_KEY_SYMROOT] + "/", "")
446
+ target_context[:build_intermediate_dir] = target_context[BUILD_KEY_TARGET_TEMP_DIR].gsub(target_context[BUILD_KEY_OBJROOT] + "/", "")
447
+ if target_context[BUILD_KEY_MODULEMAP_FILE]
448
+ target_context[BUILD_KEY_MODULEMAP_FILE] = zabel_get_content_without_pwd target_context[BUILD_KEY_MODULEMAP_FILE]
449
+ end
450
+
451
+ target_context = target_context.clone
452
+ target_context.delete(:dependency_files)
453
+ target_context.delete(:target_status)
454
+ target_context.delete(:potential_hit_target_cache_dirs)
455
+ target_context.delete(:target_md5_content)
456
+ target_context.delete(:miss_dependency_list)
457
+ [BUILD_KEY_SYMROOT, BUILD_KEY_CONFIGURATION_BUILD_DIR, BUILD_KEY_OBJROOT, BUILD_KEY_TARGET_TEMP_DIR, BUILD_KEY_PODS_XCFRAMEWORKS_BUILD_DIR, BUILD_KEY_SRCROOT].each do | key |
458
+ target_context.delete(key)
459
+ end
460
+
461
+ File.write(target_cache_dir + "/" + FILE_NAME_CONTEXT, target_context.to_yaml)
462
+ File.write(target_cache_dir + "/" + FILE_NAME_MESSAGE, message)
463
+
464
+ return true
465
+ end
466
+
467
+ def self.zabel_post(argv)
468
+
469
+ if argv.nil?
470
+ argv = {}
471
+ end
472
+
473
+ # configuration_name = nil
474
+ configuration_name = "Debug"
475
+
476
+ # if argv.include?("-configuration")
477
+ # configuration_name = argv[argv.index("-configuration") + 1]
478
+ # elsif argv.include?("--configuration")
479
+ # configuration_name = argv[argv.index("--configuration") + 1]
480
+ # end
481
+ # unless configuration_name and configuration_name.size > 0
482
+ # raise "[XCCACHE/E] -configuration or --configuration should be set"
483
+ # end
484
+
485
+ start_time = Time.now
486
+
487
+ add_count = 0
488
+
489
+ projects = zabel_get_projects
490
+
491
+ post_targets_context = {}
492
+
493
+ projects.each do | project |
494
+ project_configuration = project.build_configurations.detect { | config | config.name == configuration_name}
495
+ unless project_configuration
496
+ puts "[XCCACHE/E] #{project.path} should have config #{configuration_name}"
497
+ next
498
+ end
499
+ project.native_targets.each do | target |
500
+ target_context_file = "#{project.path}/#{target.name}.#{FILE_NAME_TARGET_CONTEXT}"
501
+ unless File.exist? target_context_file
502
+ next
503
+ end
504
+ if zabel_can_cache_target(target)
505
+ target_context = YAML.load(File.read(target_context_file))
506
+
507
+ if target_context[:target_status] == STATUS_MISS_AND_READY
508
+ environment_valid = true
509
+ [BUILD_KEY_SYMROOT, BUILD_KEY_CONFIGURATION_BUILD_DIR, BUILD_KEY_OBJROOT, BUILD_KEY_TARGET_TEMP_DIR, BUILD_KEY_SRCROOT, BUILD_KEY_FULL_PRODUCT_NAME].sort.each do | key |
510
+ unless target_context.has_key? key and target_context[key] and target_context[key].size > 0
511
+ puts "[XCCACHE/E] #{target.name} should have #{key} in #{target_context.to_s}"
512
+ environment_valid = false
513
+ break
514
+ end
515
+ end
516
+ next unless environment_valid
517
+
518
+ source_files = zabel_get_target_source_files(target)
519
+
520
+ product_dir = target_context[BUILD_KEY_CONFIGURATION_BUILD_DIR]
521
+ intermediate_dir = target_context[BUILD_KEY_TARGET_TEMP_DIR]
522
+ xcframeworks_build_dir = target_context[BUILD_KEY_PODS_XCFRAMEWORKS_BUILD_DIR]
523
+
524
+ dependency_files = zabel_get_dependency_files(target, intermediate_dir, product_dir, xcframeworks_build_dir)
525
+ if source_files.size > 0 and dependency_files.size == 0 and target.product_type != "com.apple.product-type.bundle"
526
+ puts "[XCCACHE/E] #{target.name} should have dependent files"
527
+ next
528
+ end
529
+ target_context[:dependency_files] = dependency_files - source_files
530
+ target_md5_content = zabel_get_target_md5_content(project, target, configuration_name, argv, source_files)
531
+ target_context[:target_md5_content] = target_md5_content
532
+ target_md5 = Digest::MD5.hexdigest(target_md5_content)
533
+ unless target_context[:target_md5] == target_md5
534
+ puts "[XCCACHE/E] #{target.name} md5 should not be changed after build"
535
+ next
536
+ end
537
+ if target_context[BUILD_KEY_SRCROOT] and target_context[BUILD_KEY_SRCROOT].size > 0 and
538
+ target_context[BUILD_KEY_MODULEMAP_FILE] and target_context[BUILD_KEY_MODULEMAP_FILE].size > 0
539
+ if File.exist? Dir.pwd + "/" + zabel_get_content_without_pwd("#{target_context[BUILD_KEY_SRCROOT]}/#{target_context[BUILD_KEY_MODULEMAP_FILE]}")
540
+ target_context[BUILD_KEY_MODULEMAP_FILE] = zabel_get_content_without_pwd("#{target_context[BUILD_KEY_SRCROOT]}/#{target_context[BUILD_KEY_MODULEMAP_FILE]}")
541
+ else
542
+ puts "[XCCACHE/E] #{target.name} #{target_context[BUILD_KEY_MODULEMAP_FILE]} should be supported"
543
+ next
544
+ end
545
+ end
546
+ elsif target_context[:target_status] == STATUS_HIT
547
+ if target_context[BUILD_KEY_MODULEMAP_FILE] and target_context[BUILD_KEY_MODULEMAP_FILE].size > 0
548
+ if not File.exist? Dir.pwd + "/" + target_context[BUILD_KEY_MODULEMAP_FILE]
549
+ puts "[XCCACHE/E] #{target.name} #{target_context[BUILD_KEY_MODULEMAP_FILE]} should be supported"
550
+ next
551
+ end
552
+ end
553
+ else
554
+ puts "[XCCACHE/E] #{target.name} should be hit or miss"
555
+ next
556
+ end
557
+
558
+ post_targets_context[target] = target_context
559
+ end
560
+ end
561
+ end
562
+
563
+ projects.each do | project |
564
+ project.native_targets.each do | target |
565
+ if post_targets_context.has_key? target
566
+ target_context = post_targets_context[target]
567
+ next unless target_context[:target_status] == STATUS_MISS_AND_READY
568
+
569
+ dependency_targets_set = Set.new
570
+ implicit_dependencies = []
571
+
572
+ post_targets_context.each do | other_target, other_target_context |
573
+ next if other_target == target
574
+
575
+ next if target.product_type == "com.apple.product-type.bundle"
576
+ next if other_target.product_type == "com.apple.product-type.bundle"
577
+
578
+ target_context[:dependency_files].each do | dependency |
579
+
580
+ if other_target_context[BUILD_KEY_CONFIGURATION_BUILD_DIR] and other_target_context[BUILD_KEY_CONFIGURATION_BUILD_DIR].size > 0 and
581
+ dependency.start_with? other_target_context[BUILD_KEY_CONFIGURATION_BUILD_DIR] + "/"
582
+ dependency_targets_set.add other_target
583
+ implicit_dependencies.push dependency
584
+ elsif other_target_context[BUILD_KEY_TARGET_TEMP_DIR] and other_target_context[BUILD_KEY_TARGET_TEMP_DIR].size > 0 and
585
+ dependency.start_with? other_target_context[BUILD_KEY_TARGET_TEMP_DIR] + "/"
586
+ dependency_targets_set.add other_target
587
+ implicit_dependencies.push dependency
588
+ elsif other_target_context[:build_product_dir] and other_target_context[:build_product_dir].size > 0 and
589
+ dependency.start_with? target_context[BUILD_KEY_SYMROOT] + "/" + other_target_context[:build_product_dir] + "/"
590
+ dependency_targets_set.add other_target
591
+ implicit_dependencies.push dependency
592
+ elsif other_target_context[:build_intermediate_dir] and other_target_context[:build_intermediate_dir].size > 0 and
593
+ dependency.start_with? target_context[BUILD_KEY_OBJROOT] + "/" + other_target_context[:build_intermediate_dir] + "/"
594
+ dependency_targets_set.add other_target
595
+ implicit_dependencies.push dependency
596
+ end
597
+
598
+ unless zabel_should_not_detect_module_map_dependency
599
+ if other_target_context[BUILD_KEY_MODULEMAP_FILE] and other_target_context[BUILD_KEY_MODULEMAP_FILE].size > 0 and
600
+ dependency == Dir.pwd + "/" + other_target_context[BUILD_KEY_MODULEMAP_FILE]
601
+ dependency_targets_set.add other_target
602
+ end
603
+ end
604
+ end
605
+
606
+ target_context[:dependency_files] = target_context[:dependency_files] - implicit_dependencies
607
+
608
+ end
609
+
610
+ target_context[:dependency_files] = target_context[:dependency_files] - implicit_dependencies
611
+ dependency_files_md5 = []
612
+ should_not_cache = false
613
+ target_context[:dependency_files].each do | file |
614
+ if file.start_with? target_context[BUILD_KEY_OBJROOT] + "/" or file.start_with? target_context[BUILD_KEY_SYMROOT] + "/"
615
+ puts "[XCCACHE/W] #{target.name} #{file} dependecy should not include build path"
616
+ should_not_cache = true
617
+ break
618
+ end
619
+ dependency_files_md5.push [zabel_get_content_without_pwd(file), zabel_get_file_md5(file)]
620
+ end
621
+ next if should_not_cache
622
+
623
+ target_context[:dependency_files_md5] = dependency_files_md5.sort.uniq
624
+
625
+ dependency_targets_md5 = dependency_targets_set.to_a.map { | target | [target.name, post_targets_context[target][:target_md5]]}
626
+ target_context[:dependency_targets_md5] = dependency_targets_md5
627
+
628
+ message = target_context[:target_md5_content]
629
+
630
+ if zabel_add_cache(target, target_context, message)
631
+ add_count = add_count + 1
632
+ end
633
+ end
634
+ end
635
+ end
636
+
637
+ puts "[XCCACHE/I] 恢复"
638
+ projects.each do | project |
639
+ zabel_restore_project(project)
640
+ end
641
+
642
+ zabel_keep
643
+
644
+ puts "[XCCACHE/I] total add #{add_count}"
645
+
646
+ puts "[XCCACHE/I] duration = #{(Time.now - start_time).to_i} s in stage post"
647
+
648
+ #zd_delete_main_target_shell
649
+ end
650
+
651
+ def self.zabel_get_potential_hit_target_cache_dirs(target, target_md5, miss_dependency_list)
652
+ dependency_start_time = Time.now
653
+ target_cache_dirs = Dir.glob(zabel_get_cache_root + "/" + target.name + "-" + target_md5 + "-*")
654
+ file_time_hash = {}
655
+ target_cache_dirs.each do | file |
656
+ file_time_hash[file] = File.mtime(file)
657
+ end
658
+ target_cache_dirs = target_cache_dirs.sort_by {|file| - file_time_hash[file].to_f}
659
+ potential_hit_target_cache_dirs = []
660
+ target_cache_dirs.each do | target_cache_dir |
661
+ next unless File.exist? target_cache_dir + "/" + FILE_NAME_PRODUCT
662
+ next unless File.exist? target_cache_dir + "/" + FILE_NAME_CONTEXT
663
+ target_context = YAML.load(File.read(target_cache_dir + "/" + FILE_NAME_CONTEXT))
664
+ dependency_miss = false
665
+ target_context[:dependency_files_md5].each do | item |
666
+ dependency_file = item[0]
667
+ dependency_md5 = item[1]
668
+
669
+ unless File.exist? dependency_file
670
+ miss_dependency_list.push "[XCCACHE/W] #{target.name} #{dependency_file} file should exist to be hit"
671
+ dependency_miss = true
672
+ break
673
+ end
674
+ unless zabel_get_file_md5(dependency_file) == dependency_md5
675
+ miss_dependency_list.push "[XCCACHE/W] #{target.name} #{dependency_file} md5 #{zabel_get_file_md5(dependency_file)} should match #{dependency_md5} to be hit"
676
+ dependency_miss = true
677
+ break
678
+ end
679
+ end
680
+ if not dependency_miss
681
+ if not target_context[:target_md5] == target_md5
682
+ command = "rm -rf \"#{target_cache_dir}\""
683
+ raise unless system command
684
+ puts "[XCCACHE/E] #{target.name} #{target_cache_dir} target md5 should match to be verified"
685
+ dependency_miss = false
686
+ next
687
+ end
688
+ if not target_context[:product_md5] == zabel_get_file_md5(target_cache_dir + "/" + FILE_NAME_PRODUCT)
689
+ command = "rm -rf \"#{target_cache_dir}\""
690
+ raise unless system command
691
+ puts "[XCCACHE/E] #{target.name} #{target_cache_dir} product md5 should match to be verified"
692
+ dependency_miss = false
693
+ next
694
+ end
695
+
696
+ potential_hit_target_cache_dirs.push target_cache_dir
697
+ if target_context[:dependency_targets_md5].size == 0
698
+ break
699
+ end
700
+ if potential_hit_target_cache_dirs.size > 10
701
+ break
702
+ end
703
+ end
704
+ end
705
+ return potential_hit_target_cache_dirs
706
+ end
707
+
708
+ def self.zabel_disable_build_and_inject_extract(project, target, target_context)
709
+ target_cache_dir = target_context[:hit_target_cache_dir]
710
+
711
+ # touch to update mtime
712
+ raise unless system "touch \"#{target_cache_dir}\""
713
+
714
+ # delete build phases to disable build command
715
+ target.build_phases.delete_if { | build_phase |
716
+ build_phase.class == Xcodeproj::Project::Object::PBXHeadersBuildPhase or
717
+ build_phase.class == Xcodeproj::Project::Object::PBXSourcesBuildPhase or
718
+ build_phase.class == Xcodeproj::Project::Object::PBXResourcesBuildPhase
719
+ }
720
+
721
+ # zabel_exec = "\"#{$0}\""
722
+ # if ENV["BUNDLE_BIN_PATH"] and ENV["BUNDLE_BIN_PATH"].size > 0 and ENV["BUNDLE_GEMFILE"] and ENV["BUNDLE_GEMFILE"].size > 0
723
+ # zabel_exec = "cd \"#{File.dirname(ENV["BUNDLE_GEMFILE"])}\" && \"#{ENV["BUNDLE_BIN_PATH"]}\" exe zabel"
724
+ # end
725
+ # extract_script = "#{zabel_exec} #{STAGE_EXTRACT} \"#{target_cache_dir}\" \"#{target_context[:build_product_dir]}\" \"#{target_context[:build_intermediate_dir]}\""
726
+
727
+ inject_phase = target.new_shell_script_build_phase("zabel_extract_#{target.name}")
728
+ inject_phase.shell_path = "/usr/bin/env bash -l"
729
+ inject_phase.shell_script = "export LC_ALL=en_US.UTF-8 \nexport LANG=en_US.UTF-8 \n\npod xcextract #{target_cache_dir}"
730
+ inject_phase.show_env_vars_in_log = '1'
731
+
732
+ puts "成功注入pod extract 脚本"
733
+ end
734
+
735
+ #####################################################################
736
+
737
+ def self.zabel_inject_printenv(project, target)
738
+ # zabel_exec = "\"#{$0}\""
739
+ # if ENV["BUNDLE_BIN_PATH"] and ENV["BUNDLE_BIN_PATH"].size > 0 and ENV["BUNDLE_GEMFILE"] and ENV["BUNDLE_GEMFILE"].size > 0
740
+ # zabel_exec = "cd \"#{File.dirname(ENV["BUNDLE_GEMFILE"])}\" && \"#{ENV["BUNDLE_BIN_PATH"]}\" exe zabel"
741
+ # end
742
+ # inject_phase = target.new_shell_script_build_phase("zabel_printenv_#{target.name}")
743
+ # inject_phase.shell_script = "#{zabel_exec} #{STAGE_PRINTENV} #{target.name} \"#{project.path}\""
744
+
745
+ zabel_exec = "export LC_ALL=en_US.UTF-8 \nexport LANG=en_US.UTF-8 \n\npod xcprintenv --targetname=#{target.name} --projectpath=#{project.path}"
746
+ inject_phase = target.new_shell_script_build_phase("zabel_printenv_#{target.name}")
747
+ inject_phase.shell_path = "/usr/bin/env bash -l"
748
+ inject_phase.shell_script = zabel_exec
749
+ inject_phase.show_env_vars_in_log = '1'
750
+ end
751
+
752
+ def self.zabel_pre(argv)
753
+
754
+ if argv.nil?
755
+ argv = {}
756
+ end
757
+
758
+ # configuration_name = nil
759
+ configuration_name = "Debug"
760
+
761
+ # if argv.include?("-configuration")
762
+ # configuration_name = argv[argv.index("-configuration") + 1]
763
+ # elsif argv.include?("--configuration")
764
+ # configuration_name = argv[argv.index("--configuration") + 1]
765
+ # end
766
+ # unless configuration_name and configuration_name.size > 0
767
+ # raise "[XCCACHE/E] -configuration or --configuration should be set"
768
+ # end
769
+
770
+ start_time = Time.now
771
+
772
+ if ENV["ZABEL_CLEAR_ALL"] == "YES"
773
+ command = "rm -rf \"#{zabel_get_cache_root}\""
774
+ puts command
775
+ raise unless system command
776
+ end
777
+
778
+ zabel_clean_temp_files
779
+
780
+ projects = zabel_get_projects
781
+
782
+ pre_targets_context = {}
783
+
784
+ hit_count = 0
785
+ miss_count = 0
786
+ hit_target_md5_cache_set = Set.new
787
+ iteration_count = 0
788
+
789
+ projects.each do | project |
790
+ project_configuration = project.build_configurations.detect { | config | config.name == configuration_name}
791
+ unless project_configuration
792
+ puts "[XCCACHE/E] #{project.path} should have config #{configuration_name}"
793
+ next
794
+ end
795
+ project.native_targets.each do | target |
796
+ if zabel_can_cache_target(target)
797
+ source_files = zabel_get_target_source_files(target)
798
+ unless source_files.size >= zabel_get_min_source_file_count
799
+ puts "[XCCACHE/I] skip #{target.name} #{source_files.size} < #{zabel_get_min_source_file_count}"
800
+ next
801
+ end
802
+ target_md5_content = zabel_get_target_md5_content(project, target, configuration_name, argv, source_files)
803
+ target_md5 = Digest::MD5.hexdigest(target_md5_content)
804
+ miss_dependency_list = []
805
+ potential_hit_target_cache_dirs = zabel_get_potential_hit_target_cache_dirs(target, target_md5, miss_dependency_list)
806
+
807
+ target_context = {}
808
+ target_context[:target_md5] = target_md5
809
+ target_context[:potential_hit_target_cache_dirs] = potential_hit_target_cache_dirs
810
+ target_context[:miss_dependency_list] = miss_dependency_list
811
+ if potential_hit_target_cache_dirs.size == 0
812
+ if miss_dependency_list.size > 0
813
+ puts miss_dependency_list.uniq.join("\n")
814
+ end
815
+ puts "[XCCACHE/I] miss #{target.name} #{target_md5} in iteration #{iteration_count}"
816
+ target_context[:target_status] = STATUS_MISS
817
+ miss_count = miss_count + 1
818
+ end
819
+ pre_targets_context[target] = target_context
820
+ end
821
+ end
822
+ end
823
+
824
+ while true
825
+ iteration_count = iteration_count + 1
826
+ confirm_count = hit_count + miss_count
827
+ projects.each do | project |
828
+ project.native_targets.each do | target |
829
+ next unless pre_targets_context.has_key? target
830
+ target_context = pre_targets_context[target]
831
+ next if target_context[:target_status] == STATUS_MISS
832
+ next if target_context[:target_status] == STATUS_HIT
833
+ potential_hit_target_cache_dirs = target_context[:potential_hit_target_cache_dirs]
834
+ next if potential_hit_target_cache_dirs.size == 0
835
+
836
+ hit_target_cache_dir = nil
837
+ potential_hit_target_cache_dirs.each do | target_cache_dir |
838
+ next unless File.exist? target_cache_dir + "/" + FILE_NAME_CONTEXT
839
+ hit_target_context = YAML.load(File.read(target_cache_dir + "/" + FILE_NAME_CONTEXT))
840
+ hit_target_cache_dir = target_cache_dir
841
+ hit_target_context[:dependency_targets_md5].each do | item |
842
+ dependency_target = item[0]
843
+ dependency_target_md5 = item[1]
844
+
845
+ # cycle dependency targets will be miss every time.
846
+ # TODO: to detect cycle dependency so that cache will not be added,
847
+ # or to hit cache together with some kind of algorithms.
848
+ unless hit_target_md5_cache_set.include? "#{dependency_target}-#{dependency_target_md5}"
849
+ hit_target_cache_dir = nil
850
+ break
851
+ end
852
+ end
853
+ if hit_target_cache_dir
854
+ target_context = target_context.merge!(hit_target_context)
855
+ break
856
+ end
857
+ end
858
+ if hit_target_cache_dir
859
+ puts "[XCCACHE/I] hit #{target.name} #{target_context[:target_md5]} in iteration #{iteration_count} potential #{potential_hit_target_cache_dirs.size}"
860
+ target_context[:target_status] = STATUS_HIT
861
+ target_context[:hit_target_cache_dir] = hit_target_cache_dir
862
+ hit_count = hit_count + 1
863
+ hit_target_md5_cache_set.add "#{target.name}-#{target_context[:target_md5]}"
864
+ end
865
+ end
866
+ end
867
+ if hit_count + miss_count == confirm_count
868
+ break
869
+ end
870
+ end
871
+
872
+ projects.each do | project |
873
+ should_save = false
874
+ project.native_targets.each do | target |
875
+ next unless pre_targets_context.has_key? target
876
+ target_context = pre_targets_context[target]
877
+
878
+ if target_context[:target_status] == STATUS_HIT
879
+ zabel_disable_build_and_inject_extract(project, target, target_context)
880
+ else
881
+ unless target_context[:target_status] == STATUS_MISS
882
+ target_context[:target_status] = STATUS_MISS
883
+ miss_dependency_list = target_context[:miss_dependency_list]
884
+ if miss_dependency_list.size > 0
885
+ puts miss_dependency_list.uniq.join("\n")
886
+ end
887
+ puts "[XCCACHE/I] miss #{target.name} #{target_context[:target_md5]} in iteration #{iteration_count}"
888
+ miss_count = miss_count + 1
889
+ end
890
+ zabel_inject_printenv(project, target)
891
+ end
892
+ File.write("#{project.path}/#{target.name}.#{FILE_NAME_TARGET_CONTEXT}", target_context.to_yaml)
893
+
894
+ should_save = true
895
+ end
896
+
897
+ if should_save
898
+ zabel_backup_project(project)
899
+ project.save
900
+ else
901
+ zabel_clean_backup_project(project)
902
+ end
903
+ end
904
+
905
+ #MARK: 给主工程注入执行脚本
906
+ zd_inject_main_target_shell
907
+
908
+ puts "[XCCACHE/I] total #{hit_count + miss_count} hit #{hit_count} miss #{miss_count} iteration #{iteration_count}"
909
+
910
+ puts "[XCCACHE/I] duration = #{(Time.now - start_time).to_i} s in stage pre"
911
+ end
912
+
913
+ def self.zabel_extract(target_cache_dir)
914
+ puts "[XCCACHE/D] #{Time.now.to_f.to_s}"
915
+
916
+ #target_cache_dir = ARGV[1]
917
+
918
+ cache_product_path = target_cache_dir + "/#{FILE_NAME_PRODUCT}"
919
+
920
+ start_time = Time.now
921
+
922
+ [BUILD_KEY_SYMROOT, BUILD_KEY_CONFIGURATION_BUILD_DIR, BUILD_KEY_OBJROOT, BUILD_KEY_TARGET_TEMP_DIR, BUILD_KEY_SRCROOT, BUILD_KEY_FULL_PRODUCT_NAME].sort.each do | key |
923
+ unless ENV.has_key? key and ENV[key] and ENV[key].size > 0
924
+ raise "[XCCACHE/E] #{target.name} should have #{key}"
925
+ break
926
+ end
927
+ end
928
+
929
+ if ENV[BUILD_KEY_CONFIGURATION_BUILD_DIR] != ENV[BUILD_KEY_TARGET_BUILD_DIR]
930
+ command = "mkdir -p \"#{ENV[BUILD_KEY_CONFIGURATION_BUILD_DIR]}\" && cd \"#{File.dirname(ENV[BUILD_KEY_CONFIGURATION_BUILD_DIR])}/\" && tar -xf \"#{cache_product_path}\""
931
+ puts command
932
+ raise unless system command
933
+
934
+ command = "rm -rf \"#{ENV[BUILD_KEY_TARGET_BUILD_DIR]+"/"+ENV[BUILD_KEY_FULL_PRODUCT_NAME]}\""
935
+ puts command
936
+ raise unless system command
937
+
938
+ command = "mkdir -p \"#{File.dirname(ENV[BUILD_KEY_TARGET_BUILD_DIR]+"/"+ENV[BUILD_KEY_FULL_PRODUCT_NAME])}\""
939
+ puts command
940
+ raise unless system command
941
+
942
+ command = "mv \"#{ENV[BUILD_KEY_CONFIGURATION_BUILD_DIR]+"/"+ENV[BUILD_KEY_FULL_PRODUCT_NAME]}\" \"#{ENV[BUILD_KEY_TARGET_BUILD_DIR]+"/"+ENV[BUILD_KEY_FULL_PRODUCT_NAME]}\""
943
+ puts command
944
+ raise unless system command
945
+
946
+ command = "/bin/ln -sfh \"#{ENV[BUILD_KEY_TARGET_BUILD_DIR]+"/"+ENV[BUILD_KEY_FULL_PRODUCT_NAME]}\" \"#{ENV[BUILD_KEY_CONFIGURATION_BUILD_DIR]+"/"+ENV[BUILD_KEY_FULL_PRODUCT_NAME]}\""
947
+ puts command
948
+ raise unless system command
949
+ else
950
+ command = "mkdir -p \"#{ENV[BUILD_KEY_CONFIGURATION_BUILD_DIR]}\" && cd \"#{File.dirname(ENV[BUILD_KEY_CONFIGURATION_BUILD_DIR])}/\" && tar -xf \"#{cache_product_path}\""
951
+ puts command
952
+ raise unless system command
953
+ end
954
+
955
+ puts "[XCCACHE/I] duration = #{(Time.now - start_time).to_i} s in stage extract"
956
+ puts "[XCCACHE/D] #{Time.now.to_f.to_s}"
957
+ end
958
+
959
+ def self.zabel_printenv(target_name, project_path)
960
+ # puts ARGV.to_s
961
+ # target_name = ARGV[1]
962
+ # project_path = ARGV[2]
963
+
964
+ target_context = YAML.load(File.read("#{project_path}/#{target_name}.#{FILE_NAME_TARGET_CONTEXT}"))
965
+
966
+ # see https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html
967
+ [BUILD_KEY_SYMROOT, BUILD_KEY_CONFIGURATION_BUILD_DIR, BUILD_KEY_OBJROOT, BUILD_KEY_TARGET_TEMP_DIR, BUILD_KEY_PODS_XCFRAMEWORKS_BUILD_DIR, BUILD_KEY_MODULEMAP_FILE, BUILD_KEY_SRCROOT, BUILD_KEY_FULL_PRODUCT_NAME].sort.each do | key |
968
+ if ENV[key]
969
+ target_context[key] = ENV[key]
970
+ end
971
+ end
972
+ target_context[:target_status] = STATUS_MISS_AND_READY
973
+ File.write("#{project_path}/#{target_name}.#{FILE_NAME_TARGET_CONTEXT}", target_context.to_yaml)
974
+ end
975
+
976
+ def self.zabel_clean
977
+ zabel_clean_temp_files
978
+ end
979
+
980
+
981
+ ################### 新加函数 ####################
982
+
983
+ # 获取主工程 和 主target
984
+ def self.zd_get_main_project_and_target
985
+ main_xcodeproj_path_arry = Dir.glob("*.xcodeproj")
986
+ if main_xcodeproj_path_arry.size == 0
987
+ main_xcodeproj_path_arry = Dir.glob("#{File.basename(Dir.pwd.to_s)}/*.xcodeproj")
988
+ end
989
+ if main_xcodeproj_path_arry.size == 0
990
+ puts "[XCCACHE/E 未找到 project]"
991
+ return nil
992
+ end
993
+
994
+ main_xcodeproj_path = main_xcodeproj_path_arry[0]
995
+ puts "[XCCACHE/I] 主工程路径 = #{main_xcodeproj_path}"
996
+
997
+ main_project = Xcodeproj::Project.open(main_xcodeproj_path)
998
+
999
+ main_project_name = File.basename(main_xcodeproj_path, ".*")
1000
+ main_target = nil
1001
+ main_project.native_targets.each do | target |
1002
+ if target.name == main_project_name
1003
+ main_target = target
1004
+ break
1005
+ end
1006
+ end
1007
+ puts "[XCCACHE/E 未找到 target]" unless main_project
1008
+
1009
+ return main_project, main_target
1010
+ end
1011
+
1012
+ # 在主工程中注入脚本
1013
+ def self.zd_inject_main_target_shell
1014
+ project, target = zd_get_main_project_and_target
1015
+
1016
+ main_target_shell_name = "#{MAIN_PROJECT_SHELL_NAME}_#{target.name}"
1017
+
1018
+ targetIndex = target.build_phases.index { |build_phase|
1019
+ build_phase.class == Xcodeproj::Project::Object::PBXShellScriptBuildPhase and build_phase.name == main_target_shell_name
1020
+ }
1021
+
1022
+ if targetIndex.nil?
1023
+ inject_phase = target.new_shell_script_build_phase("#{MAIN_PROJECT_SHELL_NAME}_#{target.name}")
1024
+ inject_phase.shell_path = "/usr/bin/env bash -l"
1025
+ inject_phase.shell_script = "export LC_ALL=en_US.UTF-8 \nexport LANG=en_US.UTF-8 \n\npod xccache"
1026
+ #inject_phase.run_only_for_deployment_postprocessing = "1"
1027
+ inject_phase.show_env_vars_in_log = '1'
1028
+
1029
+ puts "[XCCACHE/I] 在主target中注入 XCCache 脚本"
1030
+ else
1031
+ puts "[XCCACHE/I] 主target中已存在 XCCache 脚本"
1032
+ end
1033
+ project.save
1034
+ end
1035
+
1036
+ # 删除主工程注入的脚本
1037
+ def self.zd_delete_main_target_shell
1038
+ puts "[XCCACHE/I] 删除主工程注入的脚本"
1039
+ project, target = zd_get_main_project_and_target
1040
+ main_target_shell_name = "#{MAIN_PROJECT_SHELL_NAME}_#{target_name}"
1041
+
1042
+ target.build_phases.delete_if { | build_phase |
1043
+ build_phase.class == Xcodeproj::Project::Object::PBXShellScriptBuildPhase and build_phase.name == main_target_shell_name
1044
+ }
1045
+ project.save
1046
+ end
1047
+ end