update_channnel_sdk_v2.py 18 KB

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