cocoapods-xccache 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/cocoapods-xccache/command/xccache.rb +60 -0
- data/lib/cocoapods-xccache/command/xcextract.rb +51 -0
- data/lib/cocoapods-xccache/command/xcprepare.rb +69 -0
- data/lib/cocoapods-xccache/command/xcprintenv.rb +60 -0
- data/lib/cocoapods-xccache/command.rb +4 -0
- data/lib/cocoapods-xccache/core/zabel.rb +1047 -0
- data/lib/cocoapods-xccache/gem_version.rb +4 -0
- data/lib/cocoapods-xccache.rb +1 -0
- data/lib/cocoapods_plugin.rb +19 -0
- metadata +94 -0
@@ -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
|