channel_sdk_pretreatment.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. import zipfile
  2. import os, io, platform, subprocess
  3. from xml.etree import ElementTree as ET
  4. from V2 import file_utils, path_utils
  5. androidNS = 'http://schemas.android.com/apk/res/android'
  6. def update_channel(channel):
  7. channel_path = path_utils.get_sdk_channel_path(channel)
  8. file_utils.delete_folder(channel_path)
  9. svn_up_cmd = "svn up %s" % channel_path
  10. print("svn_up_cmd:%s" % svn_up_cmd)
  11. ret, result = exec_common_cmd(svn_up_cmd)
  12. if ret:
  13. print("svn update fail.")
  14. return
  15. if not os.path.exists(channel_path):
  16. print("no such channel")
  17. return
  18. pretreatment_project = os.path.join(channel_path, "pretreatment_project")
  19. if not os.path.exists(pretreatment_project):
  20. os.mkdir(pretreatment_project)
  21. # 渠道使用jar包接入
  22. jars_path = os.path.join(channel_path, 'jars')
  23. base_manifest_path = path_utils.get_sdk_channel_path('SDKManifest.xml')
  24. pre_manifest_path = os.path.join(pretreatment_project, 'SDKManifest.xml')
  25. if not os.path.exists(jars_path):
  26. file_utils.copy_file(base_manifest_path, pre_manifest_path)
  27. else:
  28. file_utils.copyAllFile(jars_path, pretreatment_project)
  29. # 渠道使用aar接入
  30. aars_path = os.path.join(channel_path, 'aars')
  31. if not os.path.exists(aars_path):
  32. os.mkdir(aars_path)
  33. # 复制平台SDK 到aar 目录
  34. platform_sdk_path = path_utils.get_full_path('platform_sdk', 'hnyy')
  35. file_utils.copyAllFile(platform_sdk_path, aars_path)
  36. # 解析 所有aar包
  37. decompile_aar(aars_path, pretreatment_project)
  38. # 渠道使用gradle 依赖包接入
  39. sdk_dependencies_gralde_path = os.path.join(channel_path, 'dependencies', 'build.gradle')
  40. if os.path.exists(sdk_dependencies_gralde_path):
  41. execute_gradlew(sdk_dependencies_gralde_path, pretreatment_project)
  42. pack_jar(pretreatment_project)
  43. remove_app_name(pretreatment_project)
  44. def decompile_aar(channel_aar_dir, pretreatment_project):
  45. for lib in os.listdir(channel_aar_dir):
  46. if lib.endswith(".aar"):
  47. aar_decom_dir = os.path.join(channel_aar_dir, lib.split('.aar')[0])
  48. if os.path.exists(aar_decom_dir):
  49. file_utils.delete_folder(aar_decom_dir)
  50. os.mkdir(aar_decom_dir)
  51. aar_path = os.path.join(channel_aar_dir, lib)
  52. zip_file = zipfile.ZipFile(aar_path)
  53. for names in zip_file.namelist():
  54. zip_file.extract(names, aar_decom_dir)
  55. zip_file.close()
  56. print("copy sdk resource:%s" % aar_decom_dir)
  57. copy_aar_jar_to_libs(aar_decom_dir, pretreatment_project, str(lib))
  58. sdk_base_xml = os.path.join(pretreatment_project, "SDKManifest.xml")
  59. aar_decom_dir_manifest_xml = os.path.join(aar_decom_dir, 'AndroidManifest.xml')
  60. print("merge sdk manifest:%s" % aar_decom_dir_manifest_xml)
  61. merge_manifest_aar_to_sdk(aar_decom_dir_manifest_xml, sdk_base_xml)
  62. elif lib.endswith(".jar"):
  63. pre_libs_dir = os.path.join(pretreatment_project, "lib")
  64. if not os.path.exists(pre_libs_dir):
  65. os.makedirs(pre_libs_dir)
  66. jar_path = os.path.join(channel_aar_dir, lib)
  67. lib_path = os.path.join(pre_libs_dir, lib)
  68. print("copy sdk jar:%s" % jar_path)
  69. file_utils.copy_file(jar_path, lib_path)
  70. else:
  71. print('not support type')
  72. file_utils.delete_folder(lib)
  73. continue
  74. def merge_manifest_aar_to_sdk(manifest_from, manifest_to):
  75. """
  76. Merge aars AndroidManifest.xml to the sdk SdkManifest.xml
  77. """
  78. if not os.path.exists(manifest_to) or not os.path.exists(manifest_from):
  79. print("the manifest file is not exists.manifestTo:%s;manifestFrom:%s", manifest_to, manifest_from)
  80. return False
  81. ET.register_namespace('android', androidNS)
  82. targetTree = ET.parse(manifest_to)
  83. # 获取xml根节点
  84. targetRoot = targetTree.getroot()
  85. ET.register_namespace('android', androidNS)
  86. aarTree = ET.parse(manifest_from)
  87. aarRoot = aarTree.getroot()
  88. f = open(manifest_to)
  89. targetContent = f.read()
  90. f.close()
  91. permissionConfigNode = targetRoot.find('permissionConfig')
  92. for child in list(aarRoot):
  93. # 子节点对应的values,uses-feature,uses-permission,permission
  94. key = '{' + androidNS + '}name'
  95. val = child.get(key)
  96. if val != None and len(val) > 0:
  97. attrIndex = targetContent.find(val)
  98. # values不存在添加
  99. if -1 == attrIndex:
  100. permissionConfigNode.append(child)
  101. aarAppNode = aarRoot.find('application')
  102. sdkAppConfigNode = targetRoot.find('applicationConfig')
  103. if aarAppNode is not None:
  104. for aarChild in list(aarAppNode):
  105. key = '{' + androidNS + '}name'
  106. val = aarChild.get(key)
  107. if val is not None and len(val) > 0:
  108. attrIndex = targetContent.find(val)
  109. if -1 == attrIndex:
  110. sdkAppConfigNode.append(aarChild)
  111. targetTree.write(manifest_to, 'UTF-8')
  112. return True
  113. def copy_aar_jar_to_libs(aar_decom_dir, pretreatment_project, aar_name):
  114. pre_assets_dir = os.path.join(pretreatment_project, "assets")
  115. if not os.path.exists(pre_assets_dir):
  116. os.makedirs(pre_assets_dir)
  117. pre_libs_dir = os.path.join(pretreatment_project, "lib")
  118. if not os.path.exists(pre_libs_dir):
  119. os.makedirs(pre_libs_dir)
  120. pre_base_jni_dir = os.path.join(pretreatment_project, "jniLibs")
  121. if not os.path.exists(pre_base_jni_dir):
  122. os.makedirs(pre_base_jni_dir)
  123. pre_res_dir = os.path.join(pretreatment_project, "res")
  124. if not os.path.exists(pre_res_dir):
  125. os.makedirs(pre_res_dir)
  126. for f in os.listdir(aar_decom_dir):
  127. # 分割最后一个“.”
  128. name = aar_name.rsplit(".", 1)[0]
  129. pre_libs_name = os.path.join(pre_libs_dir, name + ".jar")
  130. if f.endswith(".jar"):
  131. file_utils.copy_file(os.path.join(aar_decom_dir, f), pre_libs_name)
  132. if "assets" == f:
  133. file_utils.copyAllFile(os.path.join(aar_decom_dir, f), pre_assets_dir)
  134. if "jni" == f:
  135. decom_jni_dir = os.path.join(aar_decom_dir, f)
  136. for jniF in os.listdir(decom_jni_dir):
  137. if "armeabi" == jniF or "armeabi-v7a" == jniF or "x86" == jniF or "arm64-v8a" == jniF \
  138. or "x86_64" == jniF:
  139. pre_jni_dir = os.path.join(pre_base_jni_dir, jniF)
  140. if not os.path.exists(pre_jni_dir):
  141. os.makedirs(pre_jni_dir)
  142. file_utils.copyAllFile(os.path.join(decom_jni_dir, jniF), pre_jni_dir)
  143. if "libs" == f:
  144. decom_libs_dir = os.path.join(aar_decom_dir, f)
  145. # 部分aar包中的classes.jar里面并没有class,而是在aar包中libs里面的classes.jar
  146. for libF in os.listdir(decom_libs_dir):
  147. if libF == "classes.jar":
  148. # 如果SDK libs里面已经存在先删除,在复制一次
  149. if os.path.exists(pre_libs_name):
  150. file_utils.del_file(pre_libs_name)
  151. file_utils.copyAllFile(os.path.join(decom_libs_dir, libF), pre_libs_name)
  152. else:
  153. file_utils.copyAllFile(os.path.join(decom_libs_dir, libF), os.path.join(pre_libs_dir, libF))
  154. if "res" == f:
  155. copy_res_to_apk(os.path.join(aar_decom_dir, f), pre_res_dir)
  156. def copy_res_to_apk(copyFrom, copyTo):
  157. """
  158. Copy two resource folders
  159. """
  160. if not os.path.exists(copyFrom):
  161. print("the copyFrom %s is not exists.", copyFrom)
  162. return
  163. if not os.path.exists(copyTo):
  164. os.makedirs(copyTo)
  165. if os.path.isfile(copyFrom) and not merge_res_xml(copyFrom, copyTo):
  166. file_utils.copy_file(copyFrom, copyTo)
  167. print("copyResToApk:copyFrom-->%s;copyTo-->%s;", copyFrom, copyTo)
  168. return
  169. # 卢-->修改复制资源,并合并和删除重复资源
  170. for f in os.listdir(copyFrom):
  171. sourcefile = os.path.join(copyFrom, f)
  172. targetfile = os.path.join(copyTo, f)
  173. if f == 'abc_action_menu_layout.xml' or f == 'abc_screen_toolbar.xml':
  174. file_utils.delete_folder(sourcefile)
  175. continue
  176. if os.path.isfile(sourcefile):
  177. if not os.path.exists(copyTo):
  178. os.makedirs(copyTo)
  179. delete_same_res(sourcefile, copyTo, 'attrs.xml')
  180. delete_same_res(sourcefile, copyTo, 'colors.xml')
  181. delete_same_res(sourcefile, copyTo, 'dimens.xml')
  182. delete_same_res(sourcefile, copyTo, 'drawables.xml')
  183. delete_same_res(sourcefile, copyTo, 'ids.xml')
  184. delete_same_res(sourcefile, copyTo, 'integers.xml')
  185. delete_same_res(sourcefile, copyTo, 'public.xml')
  186. delete_same_res(sourcefile, copyTo, 'strings.xml')
  187. delete_same_res(sourcefile, copyTo, 'styles.xml')
  188. if merge_res_xml(sourcefile, targetfile):
  189. continue
  190. destfilestream = open(targetfile, 'wb')
  191. sourcefilestream = open(sourcefile, 'rb')
  192. destfilestream.write(sourcefilestream.read())
  193. destfilestream.close()
  194. sourcefilestream.close()
  195. if os.path.isdir(sourcefile):
  196. copy_res_to_apk(sourcefile, targetfile)
  197. def merge_res_xml(copyFrom, copyTo):
  198. """
  199. Merge all android res xml
  200. """
  201. if not os.path.exists(copyTo):
  202. return False
  203. fromName = os.path.basename(copyFrom)
  204. if not fromName.endswith('.xml'):
  205. return False
  206. f = io.open(copyTo, 'r', encoding='utf-8')
  207. targetContent = f.read()
  208. f.close()
  209. fromTree = ET.parse(copyFrom)
  210. fromRoot = fromTree.getroot()
  211. toTree = ET.parse(copyTo)
  212. toRoot = toTree.getroot()
  213. for fromNode in list(fromRoot):
  214. val = fromNode.get('name')
  215. if val != None and len(val) > 0:
  216. # 如果出现name和parent字段一样也会删除,加上name=
  217. # <style name="Theme.AppCompat.Light">
  218. # <style name="m4399ad.Dialog.NoTitleBar" parent="Theme.AppCompat.Light">
  219. valMatched = 'name="' + val + '"'
  220. attrIndex = targetContent.find(valMatched)
  221. if -1 == attrIndex:
  222. toRoot.append(fromNode)
  223. # else:
  224. # log_utils.warning("The node %s is already exists in %s", val, copyTo)
  225. else:
  226. if fromNode.tag == "declare-styleable":
  227. for toNode in list(toRoot):
  228. if toNode.get("name") == fromNode.get("name"):
  229. for fNode in list(fromNode.iter("attr")):
  230. print(fNode)
  231. toNode.append(fNode)
  232. # 删除valus.xml中的无法编译的字段
  233. for toNode in list(toRoot):
  234. for declareNode in list(toNode.iter('declare-styleable')):
  235. declareName = declareNode.get('name')
  236. if (declareName == 'ActionBar' or declareName == 'Toolbar' or declareName == 'ActionMode'
  237. or declareName == 'Spinner' or declareName == 'LinearLayoutCompat'
  238. or declareName == 'AlignTextView' or declareName == 'TextSeekBar'):
  239. toRoot.remove(declareNode)
  240. continue
  241. toTree.write(copyTo, 'UTF-8')
  242. return True
  243. def delete_same_res(sourcefile, copyTo, filename):
  244. sourceName = os.path.basename(sourcefile)
  245. if not sourceName.endswith('.xml'):
  246. return
  247. targetfile = os.path.join(copyTo, filename)
  248. if not os.path.exists(targetfile):
  249. return
  250. fromTree = ET.parse(sourcefile)
  251. fromRoot = fromTree.getroot()
  252. tf = open(targetfile)
  253. targetContent = tf.read()
  254. tf.close()
  255. if filename == 'attrs.xml':
  256. for child in list(fromRoot):
  257. for declareNode in list(child.iter('declare-styleable')):
  258. if -1 == targetContent.find('"' + declareNode.get("name") + '"'):
  259. print("no same")
  260. continue
  261. else:
  262. for attrNode in list(declareNode.iter('attr')):
  263. attrName = attrNode.get('name')
  264. if attrName != None and len(attrName) > 0:
  265. attrMatched = '"' + attrName + '"'
  266. attrIndex = targetContent.find(attrMatched)
  267. if -1 == attrIndex:
  268. continue
  269. else:
  270. # for childNo in list(fromRoot):
  271. # if childNo.get("name") == attrName:
  272. # print(attrName)
  273. # fromRoot.remove(childNo)
  274. declareNode.remove(attrNode)
  275. else:
  276. for child in list(fromRoot):
  277. # 使用到com.android.support:appcompat-v7都要删除
  278. for itemNode in list(child.iter('item')):
  279. itemName = itemNode.get('name')
  280. if (
  281. itemName == 'buttonGravity' or itemName == 'subtitleTextStyle' or itemName == 'subtitleTextAppearance'
  282. or itemName == 'collapseIcon' or itemName == 'contentInsetStart' or itemName == 'contentInsetEnd'
  283. or itemName == 'contentInsetStartWithNavigation' or itemName == 'collapseContentDescription'
  284. or itemName == 'titleTextStyle' or itemName == 'titleMargin' or itemName == 'titleTextAppearance'
  285. or itemName == 'background' or itemName == 'backgroundSplit' or itemName == 'backgroundStacked'
  286. or itemName == 'displayOptions' or itemName == 'divider' or itemName == 'elevation'
  287. or itemName == 'popupTheme' or itemName == 'maxButtonHeight' or itemName == 'dividerPadding'
  288. or itemName == 'showDividers' or itemName == 'closeItemLayout'):
  289. child.remove(itemNode)
  290. continue
  291. name = child.get('name')
  292. if name != None and len(name) > 0:
  293. val = '"' + name + '"'
  294. index = targetContent.find(val)
  295. if -1 == index:
  296. continue
  297. else:
  298. fromRoot.remove(child)
  299. fromTree.write(sourcefile, 'UTF-8')
  300. def pack_jar(channel_path):
  301. """
  302. 打包所有的jar
  303. :param platform:
  304. :param apk_decompile_tmp_dir:
  305. :param channel_path:
  306. :param sdk_name:
  307. :return:
  308. """
  309. temp_gen_path = os.path.join(channel_path, 'gen')
  310. print('[temp_gen_path]: %s ' % temp_gen_path)
  311. if not os.path.exists(temp_gen_path):
  312. os.makedirs(temp_gen_path)
  313. dx = path_utils.get_d8_path()
  314. dex_cmd = ' --release --output %s' % temp_gen_path
  315. # 找到所有lib依赖,编译成class.dex
  316. sdk_lib_path = os.path.join(channel_path, 'lib')
  317. lib_list = ''
  318. for sdk_lib in file_utils.iterate_dir_path(sdk_lib_path):
  319. if sdk_lib.endswith('.DS_Store'):
  320. continue
  321. lib_list += ' ' + sdk_lib
  322. dex_cmd = dex_cmd + lib_list
  323. print('packaging all jar ...')
  324. ret = exec_jar_cmd(dx, dex_cmd)
  325. if ret:
  326. return ret
  327. print('baksmali classes.dex ...')
  328. out_dex = os.path.join(temp_gen_path, 'classes.dex')
  329. bak_smali_path = path_utils.get_baksmali_path()
  330. out_smali_path = os.path.join(temp_gen_path, 'out')
  331. ret = exec_jar_cmd(bak_smali_path, 'd "%s" -o "%s"' % (out_dex, out_smali_path))
  332. if ret:
  333. return ret
  334. # 将生成的文件拷贝到目标目录
  335. print('copy all smali ...')
  336. temp_smali_path = os.path.join(channel_path, 'smali_classes2')
  337. ret = file_utils.copy_file_all_dir(out_smali_path, temp_smali_path, True)
  338. if ret:
  339. return ret
  340. def exec_jar_cmd(jar, params):
  341. cmd = 'java -jar "%s" %s' % (jar, params)
  342. print("[exec_jar_cmd]:%s" % cmd)
  343. ret, result = exec_common_cmd(cmd)
  344. return ret
  345. def exec_common_cmd(cmd, cd=None):
  346. """
  347. 执行cmd命令
  348. 返回值:None —— 子进程尚未结束;
  349. ==0 —— 子进程正常退出;
  350. > 0—— 子进程异常退出,return code对应于出错码;
  351. < 0—— 子进程被信号杀掉了。
  352. """
  353. '''print(cmd)
  354. p = os.popen(cmd)
  355. print(p.read())''
  356. '''
  357. try:
  358. s = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, cwd=cd, encoding='utf-8')
  359. std_output, err_output = s.communicate()
  360. if platform.system() == 'Windows':
  361. std_output = std_output.decode('gbk')
  362. err_output = err_output.decode('gbk')
  363. '''
  364. None —— 子进程尚未结束;
  365. ==0 —— 子进程正常退出;
  366. > 0—— 子进程异常退出,return code对应于出错码;
  367. < 0—— 子进程被信号杀掉了。
  368. '''
  369. ret = s.returncode
  370. if ret:
  371. print('*******ERROR*******')
  372. print(std_output)
  373. print(err_output)
  374. print('*******************')
  375. cmd = 'error::' + cmd + ' !!!exec Fail!!! '
  376. else:
  377. print(std_output)
  378. print(err_output)
  379. cmd = cmd + ' !!!exec success!!! '
  380. print(cmd)
  381. except Exception as e:
  382. print('Exception ' + e)
  383. return 1, e
  384. return ret, std_output
  385. def remove_app_name(pretreatment_project):
  386. value_xml = os.path.join(pretreatment_project,'res','values','values.xml')
  387. print('remove app_name for :%s'%value_xml)
  388. fromTree = ET.parse(value_xml)
  389. fromRoot = fromTree.getroot()
  390. for child in list(fromRoot):
  391. if child.get('name') == 'app_name':
  392. print('remove app name')
  393. fromRoot.remove(child)
  394. fromTree.write(value_xml, 'UTF-8')
  395. def execute_gradlew(sdk_dependencies_gralde_path, pretreatment_project):
  396. sdk_dependencies_gralde_dir = os.path.dirname(sdk_dependencies_gralde_path)
  397. dependencies_libs_dir = os.path.join(sdk_dependencies_gralde_dir, "libs")
  398. os.makedirs(dependencies_libs_dir)
  399. cmd = '''
  400. cd %s && \
  401. /opt/mixsdk/tool/gradle/gradle-7.5.1/bin/gradle copyToLibs \
  402. ''' % sdk_dependencies_gralde_dir
  403. print(cmd)
  404. ret, result = exec_common_cmd(cmd)
  405. if not ret:
  406. decompile_aar(dependencies_libs_dir, pretreatment_project)
  407. if __name__ == '__main__':
  408. update_channel("/home/workspace_ckw/script2.0/V2/channel/huawei/", False)
  409. # deleteSameRes("/Users/kaiweicai/PycharmProjects/parse_aar/aars/attr/attrs_car.xml","/Users/kaiweicai/PycharmProjects/parse_aar/aars/attr",'attrs.xml')