scheme_desc_collector.py
16.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
# encoding: utf-8
# 收集端能力描述表:遍历头文件中注释代码,收集在plugin.h中的描述表信息
# 在工程根目录执行 python Scripts/scheme_desc_collector.py input_file_path output_file_path
# 接收两个参数 input_file_path 包含头文件list的文件路径 output_file_path:描述表的输出路径
import json
import os
import time
import re
import sys
import zipfile
import difflib
import shutil
# 当前脚本的目录
script_path = os.path.dirname(os.path.realpath(__file__))
# 全局定义 解决 eval 不识别 true & false 问题
global false, true
false =False
true=True
class Hook():
def __init__(self):
# 是否需要输出 diff 文件
self.output_diff_file = False
# 清理当前工程缓存,避免收集多余的描述表
def clean_cache():
# Easybox方式集成时需要执行box clean
if os.system("box clean --unuse-downloads") == 0:
# 清理百度系宿主下的无用二进制
cmd = "cd %s && box clean --unuse-downloads " % script_path
os.system(cmd)
# 获取 input_file_path, output_file_path
def get_path_list(self):
if len(sys.argv) == 1:
# 手动执行
project_dir = input(
"请输入需要遍历的项目目录, 例如/Users/v_zhangshengjun/baidu/product-mnpproject\n: "
).strip()
output_file_path = input(
"请输入生成的描述表路径, 例如/Users/v_zhangshengjun/baidu/product-mnpproject/Resources/SwanResource \n:"
).strip()
output_diff = input("是否需要输出更新描述表后的diff文件(y/n)\n: ").strip()
if len(project_dir) == 0:
raise ValueError('未输入项目目录 project_dir !!!')
if len(output_file_path) == 0:
raise ValueError('必须指定描述表输出路径!!!')
if output_diff == 'y':
self.output_diff_file = True
else:
# box install 或 pod install 自动执行
project_dir = sys.argv[1]
output_file_path = sys.argv[2]
if len(sys.argv) == 4 and sys.argv[3] == '--diff':
self.output_diff_file = True
# input_file_path 包含头文件list的文件路径
input_file_path = ''
if project_dir.endswith('.xcfilelist'):
# box install 时自动执行
input_file_path = project_dir
else:
if os.path.exists("{}/Pods".format(project_dir)):
# pod install 时自动执行、pod 环境下手动执行
input_file_path = "{}/Pods/scheme_descs_search_header.xcfilelist".format(project_dir)
else:
# box 环境下手动执行
input_file_path = "{}/.easybox/scheme_descs_search_header.xcfilelist".format(project_dir)
result = {}
with open(input_file_path, "w") as new_file:
for root, dirs, names in os.walk(project_dir):
for filename in names:
# 过滤文件的规则 是包含Plugin的头文件 and 相同文件只统计一次
if (filename.endswith(".h") and
(filename.find("Plugin") >= 0 or
filename.find("Bridge") >= 0)) and not result.get(filename):
file_path = os.path.join(root, filename)
new_file.write("{}\n".format(file_path))
result[filename] = file_path
new_file.close()
return [input_file_path, output_file_path]
# 解压缩新旧zip文件
def unzip_file(self, zip_src, dst_dir):
r = zipfile.is_zipfile(zip_src)
if r:
fz = zipfile.ZipFile(zip_src, 'r')
for file in fz.namelist():
fz.extract(file, dst_dir)
else:
print('This is not zip')
# 读取文件
def read_file(self, filename):
try:
with open(filename, 'r') as f:
return f.readlines()
except IOError:
print("ERROR: 没有找到文件:%s或读取文件失败!" % filename)
sys.exit(1)
# 比较两个同名的json文件
def file_has_diff(self, file1, file2):
file1_content = self.read_file(file1)
file2_content = self.read_file(file2)
if file1_content != file2_content:
filename = os.path.split(file1)[1]
print('----------文件内容不一致-----------:' + filename)
if self.output_diff_file:
self.compare_to_html(file1, file2, filename)
return True
else:
return False
# 格式化 json文件
def format_json_file(self, file):
with open(file, 'r') as old_file:
file_content = ''
dict = eval(old_file.read())
file_content = json.dumps(dict, sort_keys=True, indent=4)
old_file.close()
with open(file, "w") as new_file:
new_file.write(file_content)
new_file.close()
# 输出 html 格式的 diff文件
def compare_to_html(self, file1, file2, filename):
self.format_json_file(file1)
self.format_json_file(file2)
d = difflib.HtmlDiff()
result = d.make_file(self.read_file(file1), self.read_file(file2), 'old-file',
'new-file', True)
with open(script_path + '/' + filename + '-diff.html', 'w') as f:
f.writelines(result)
# 生成临时解压缩目录
def create_dst_dirs(self, dir_arr):
for dir in dir_arr:
os.mkdir(dir)
# 删除临时解压缩目录
def remove_dst_dirs(self, dir_arr):
for dir in dir_arr:
if os.path.exists(dir):
shutil.rmtree(dir)
# 解压两个zip文件并逐个比对同名文件是否有diff
# git比较不了zip文件,故在生成新zip文件时,用脚本进行diff然后决定是否覆盖旧zip文件
def zip_has_diff(self, output_file_path):
path = output_file_path + '/'
old_zip_src = path + 'BBAPluginDescription.zip'
if os.path.exists(old_zip_src) == False:
return True
new_zip_src = path + 'desc.zip'
old_dst_dir = path + 'BBAPluginDescription/'
new_dst_dir = path + 'desc/'
self.create_dst_dirs([old_dst_dir, new_dst_dir])
self.unzip_file(old_zip_src, old_dst_dir)
self.unzip_file(new_zip_src, new_dst_dir)
if len(os.listdir(old_dst_dir)) != len(os.listdir(new_dst_dir)):
# 输出 zip 内文件数目不一致情况
print('----------文件数目不一致-----------')
self.print_files_diff(old_dst_dir, new_dst_dir)
self.remove_dst_dirs([old_dst_dir, new_dst_dir])
return True
for filename in os.listdir(old_dst_dir):
if os.path.exists(new_dst_dir + filename):
old_file = old_dst_dir + filename
new_file = new_dst_dir + filename
if self.file_has_diff(old_file, new_file):
self.remove_dst_dirs([old_dst_dir, new_dst_dir])
return True
else:
self.remove_dst_dirs([old_dst_dir, new_dst_dir])
print('----------文件名称不一致-----------')
return True
os.remove(new_zip_src)
self.remove_dst_dirs([old_dst_dir, new_dst_dir])
return False
# 输出 zip 内文件数目不一致情况
def print_files_diff(self, old_dst_dir, new_dst_dir):
old_list = os.listdir(old_dst_dir)
new_list = os.listdir(new_dst_dir)
print('old_BBAPluginDescription.zip:{}\n'.format(old_list)+'new_BBAPluginDescription.zip:{}'.format(new_list))
if len(old_list) > len(new_list):
for filename in old_list:
if filename not in new_list:
print('BBAPluginDescription.zip 内相较更新之前少了文件:{}'.format(filename))
else:
for filename in new_list:
if filename not in os.listdir(old_dst_dir):
print('BBAPluginDescription.zip 内相较更新之前多了文件:{}'.format(filename))
# 描述表文件替换
def replace_desc(self, output_file_path):
# 在新旧zip文件有diff时,重命名 desc.zip 为 BBAPluginDescription.zip
if self.zip_has_diff(output_file_path):
os.rename(output_file_path + '/desc.zip',
output_file_path + '/BBAPluginDescription.zip')
print('描述表文件已生成在目录:{}'.format(output_file_path))
else:
print('描述表文件无变化,故不重新生成')
# 定义的jsNative规定的key
name_key = "name"
authority_key = "authority"
path_key = "path"
args_key = "args"
desc_key = "desc"
# 获取描述表字段的正则 (name, authority, path, args, config) config = {basic:{invoke:xxx , handler:xx}}
pattern01 = r'\* @name:\s*(.*)\s* \* @authority:\s*(.*)\s* \* @path:\s*(.*)(?:\s*\* @args:)((?:.|\n)*?)(?: \* @config:)((?:.|\n)*?)(?:\*)'
# 获取描述表字段的正则 json格式的描述表
pattern02 = r'\* @jsnative_desc:((?:.|\n)*?)(?:\*\/)'
class Collector():
def __init__(self, input_path, cache_path, desc_patterns=[pattern01, pattern02]):
if not os.path.exists(input_path):
raise Exception('请切换到实际目录执行,脚本会遍历此文件夹下的所以子文件获取描述表')
self.descs = {}
self.desc_compiles = []
self.input_path = input_path
self.cache_path = cache_path
for desc_pattern in desc_patterns:
self.desc_compiles.append(re.compile(desc_pattern))
# 查找文件中所有的描述表 path:str 文件路径
def __find_descs(self, path):
if sys.version_info < (3, 0):
file = open(path, 'r')
else:
file = open(path, mode='r', encoding='ascii', errors='ignore')
s = file.read()
file.close()
desc_list = []
for compile in self.desc_compiles:
desc_list.extend(compile.findall(s))
return desc_list
# 将一个描述表转换成 jsNative规范格式 desc: tuple (name, authority, path, args) result 描述表的数组
def __desc_parser(self, desc):
# 解析 tuple格式的desc
if isinstance(desc, tuple) and len(desc) >= 5:
# 解析desc部分
# 设置 name 必传
desc_item = {}
if len(desc[0]):
desc_item[name_key] = desc[0]
else:
raise Exception("name参数不存在")
# 设置 authority 必传
if len(desc[1]):
desc_item[authority_key] = desc[1]
else:
raise Exception("authority参数不存在")
# 设置 path 非必传
if len(desc[2]):
desc_item[path_key] = desc[2]
# 设置 args 非必传 jsonstring类型 default:[]
args = desc[3]
if len(args) > 0:
try:
args_objc = json.loads(args)
desc_item[args_key] = args_objc
except Exception:
raise Exception('args参数不是jsonstirng')
else:
desc_item[args_key] = []
# 解析config部分
config = desc[4]
if len(config) > 0:
try:
config_objc = json.loads(config)
except Exception:
raise Exception('config参数不是jsonstirng')
if type(config_objc) is dict:
for key, item in config_objc.items():
tmp = self.descs.get(desc_key)
if not tmp:
tmp = {}
self.descs[desc_key] = tmp
# 判断是否有重复name
if tmp.get(desc_item[name_key]) is not None:
raise Exception('name字段重复, 提供给前端的描述表中name字段要唯一')
tmp[desc_item[name_key]] = desc_item
# 设置config
for config_key, config_item in item.items():
config_key = key + '_' + config_key
tmp = self.descs.get(config_key)
if not tmp:
tmp = {}
self.descs[config_key] = tmp
tmp[desc_item[name_key]] = config_item
else:
raise Exception("config参数不存在")
# 解析JSON格式的desc
elif isinstance(desc, str) and len(desc) > 0:
try:
desc_objc = json.loads(desc)
except Exception:
raise Exception('description参数不是jsonStirng')
# config部分处理 移除 desc_objc 的config 部分
config_objc = desc_objc.pop("config")
# 判断是否有重复name
tmp = self.descs.get(desc_key)
if not tmp:
tmp = {}
self.descs[desc_key] = tmp
if tmp.get(desc_objc[name_key]) is not None:
raise Exception('name字段重复, 提供给前端的描述表中name字段要唯一')
tmp[desc_objc[name_key]] = desc_objc
for key, item in config_objc.items():
# 设置config
for config_key, config_item in item.items():
config_key = key + '_' + config_key
tmp = self.descs.get(config_key)
if not tmp:
tmp = {}
self.descs[config_key] = tmp
tmp[desc_objc[name_key]] = config_item
else:
raise Exception('描述表参数缺失,确保格式为')
def get_descs(self):
startT = time.time()
# print('收集描述表信息')
paths = {}
for line in open(self.input_path):
path = line.replace("\n", "")
fileName = os.path.basename(path)
if (not paths.get(fileName)):
paths[fileName] = path
for file_name, path in paths.items():
desclist = self.__find_descs(path)
for desc in desclist:
try:
self.__desc_parser(desc)
except Exception as e:
print('⚠️ 端能力描述表错误,请及时处理 ⚠️\npath: {}\ndescription: {}\nerror: {}'.format(path, desc, e))
exit(-1)
endT= time.time()
# print('收集描述表信息耗时:' + str(endT - startT))
return self.descs
# 存文件
def cache(self, zip_file=True):
try:
file_paths = []
for key, item in collector.descs.items():
# 解决工程路径中有中文的问题
path = ''
if sys.version_info < (3, 0):
path = os.path.join(self.cache_path, key.encode("utf-8")) + ".json"
else:
path = os.path.join(self.cache_path, key) + ".json"
file_paths.append(path)
json_str = json.dumps(item, sort_keys=True)
with open(path, 'w') as f:
f.write(json_str)
f.close()
# 压缩文件,
if zip_file:
self.zip_files(file_paths, os.path.join(self.cache_path, 'desc.zip'))
for path in file_paths:
os.remove(path)
except Exception as e:
print('⚠️ 端能力描述表收集错误,请及时处理 ⚠️ \nerror: {}'.format(e))
exit(-2)
def zip_files(self, files, zip_name):
zip = zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED)
for file in files:
zip.write(file, arcname=os.path.split(file)[1])
zip.close()
def unzip_file(self, zip_name):
zip = zipfile.ZipFile(zip_name, "r")
for filename in zip.namelist():
content = zip.read(filename)
jsonObject = json.loads(content)
filename = os.path.split(filename)[1].split('.')[0]
self.descs[filename] = jsonObject
if __name__ == '__main__':
hook = Hook()
path_list = hook.get_path_list()
input_file_path = path_list[0]
output_file_path = path_list[1]
# 是否需要压缩
zip_file = True
collector = Collector(input_path=input_file_path, cache_path=output_file_path)
collector.get_descs()
collector.cache(zip_file=zip_file)
hook.replace_desc(output_file_path)
exit(0)
# print('描述表收集成功,在 {} 路径下,请提交变更到远程仓库'.format(cache_path))