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.
- 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
|