| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441 |
- #!/usr/bin/python
- # -*- coding: utf-8 -*-
- from logging import debug, root
- from struct import pack
- from Common import Setting, Debug, PathConvert, StringFormat
- Setting.SetDefaultencodingUTF8()
- import zipfile, sys, os, traceback
- import shutil
- import platform
- import subprocess
- import xml.dom.minidom as minidom
- import ConfigParser
- CUR_PLATFORM_NAME = platform.system()
- CUR_PATH = os.path.abspath(sys.path[0])
- APK_TOOLS_PATH = os.path.abspath(CUR_PATH + '/../APKTools')
- APK_TOOL_JAR_PATH = os.path.abspath(APK_TOOLS_PATH + '/apktool.jar')
- APK_SIGNER_JAR_PATH = os.path.abspath(APK_TOOLS_PATH + '/apksigner.jar')
- if CUR_PLATFORM_NAME == 'Windows':
- ZIP_ALIGN_PATH = os.path.abspath(APK_TOOLS_PATH + '/zipalign.exe')
- elif CUR_PLATFORM_NAME == 'Linux':
- ZIP_ALIGN_PATH = os.path.abspath(APK_TOOLS_PATH + '/zipalign.exe')
- elif CUR_PLATFORM_NAME == 'Darwin':
- ZIP_ALIGN_PATH = os.path.abspath(APK_TOOLS_PATH + '/zipalign_mac')
- else:
- ZIP_ALIGN_PATH = os.path.abspath(APK_TOOLS_PATH + '/zipalign.exe')
- TEMP_OUT_PATH = r"\apkTemp"
- VALID_CMD_ARGV = {"-help", "-rulePath", "-configPath",
- "-resPath", "-outPath", "-inPath",
- "-keyStorePath", "-keyAlias", "-keyStorePwd", "-keyAliasPwd",
- "-keySignVer"}
- def PrintHelp():
- Debug.Log('')
- Debug.Log('')
- Debug.Log('命令参数说明')
- Debug.Log('')
- Debug.Log('-help 查看帮助')
- Debug.Log('')
- Debug.Log('')
- Debug.Log('-configPath 根据INI配置文件来打包')
- Debug.Log('')
- Debug.Log('')
- Debug.Log('-inPath 原始Apk路径')
- Debug.Log('-outPath 生成的新Apk路径')
- Debug.Log('-resPath 作为资源的Apk文件')
- Debug.Log('-rulePath 指定更新资源规则,可找对应程序获得当前版本的规则')
- Debug.Log(' 为.apk文件里的资源路径')
- Debug.Log(' 如:assets/AssetsAndroid')
- Debug.Log('-keyStorePath 签名证书路径')
- Debug.Log('-keyAlias 签名证书alias')
- Debug.Log('-keyStorePwd 签名证书密码')
- Debug.Log('-keyAliasPwd 签名证书alias密码')
- Debug.Log('-keySignVer (可选)签名版本, 默认为v1')
- Debug.Log('')
- Debug.Log('')
- Debug.Log('')
- def ValidJava():
- try:
- cmds = ["java", "-version"]
- sp = subprocess.Popen(cmds, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- contents = sp.communicate()
- sp.wait()
- version = None
- for content in contents:
- lines = content.split("\n")
- for i in range(len(lines)):
- line = lines[i].strip()
- if line.startswith("java version "):
- version = line.replace("java version ", "")
- version = version.replace("\"", "")
- if not version:
- Debug.LogError("无法检测JDK版本 ")
- return False
- else:
- if version.startswith("1.8"):
- return True
- else:
- Debug.LogError("JDK版本不符,需要使用1.8版本 当前版本 " + version)
- return False
- except Exception as exc:
- Debug.LogError(exc)
- Debug.Log("")
- Debug.LogError("无法找到有效JDK, 请检查环境 !!! ")
- return False
- return True
- def ToolsChmodEXE():
- if CUR_PLATFORM_NAME != 'Linux' or CUR_PLATFORM_NAME != 'Darwin':
- return
- Debug.LogSuccess("赋予%s目录可执行权限开始" % APK_TOOLS_PATH)
- import stat
- for root, dirs, files in os.walk(APK_TOOLS_PATH):
- for file in files:
- os.chmod(file, os.stat(file).st_mode | stat.S_IEXEC)
- Debug.LogSuccess("赋予%s目录可执行权限成功" % APK_TOOLS_PATH)
- def ExecConfig(configPath):
- if not os.path.exists(StringFormat.FormatStringPlatform(configPath)):
- Debug.LogError(configPath + "不存在")
- return
- config = ConfigParser.ConfigParser()
- config.read(StringFormat.FormatStringPlatform(configPath))
- keyStorePathD = config.get('signinfodefault', 'keyStorePath')
- keyAliasD = config.get('signinfodefault', 'keyAlias')
- keyStorePwdD = config.get('signinfodefault', 'keyStorePwd')
- keyAliasPwdD = config.get('signinfodefault', 'keyAliasPwd')
- keySignVerD = config.get('signinfodefault', 'keySignVer')
- packs = []
- for k in config.sections():
- if k[:4] == 'pack':
- packs.append(int(k[4:]))
- packs.sort()
- success = []
- errors = []
- for i in packs:
- oneConfig = 'pack' + str(i)
- inPath = config.get(oneConfig, 'inPath')
- outPath = config.get(oneConfig, 'outPath')
- rulePath = config.get(oneConfig, 'rulePath')
- resPath = config.get(oneConfig, 'resPath')
- if config.has_option(oneConfig, "keyStorePath"):
- keyStorePath = config.get(oneConfig, 'keyStorePath')
- keyAlias = config.get(oneConfig, 'keyAlias')
- keyStorePwd = config.get(oneConfig, 'keyStorePwd')
- keyAliasPwd = config.get(oneConfig, 'keyAliasPwd')
- keySignVer = config.get(oneConfig, 'keySignVer')
- res = ExecOne(inPath, outPath, rulePath, resPath,
- keyStorePath, keyAlias, keyStorePwd, keyAliasPwd,
- keySignVer)
- else:
- res = ExecOne(inPath, outPath, rulePath, resPath,
- keyStorePathD, keyAliasD, keyStorePwdD, keyAliasPwdD,
- keySignVerD)
- if res:
- success.append(i)
- else:
- errors.append(i)
- successNum = len(success)
- if successNum > 0:
- Debug.Log("")
- Debug.Log("")
- Debug.Log("")
- Debug.LogSuccess("执行成功的有:")
- for i in success:
- oneConfig = 'pack' + str(i)
- Debug.LogSuccess(config.get(oneConfig, 'inPath'))
- Debug.LogSuccess("\t to " + config.get(oneConfig, 'outPath'))
- else:
- Debug.Log("")
- Debug.Log("")
- errorNum = len(errors)
- if errorNum > 0:
- Debug.Log("")
- Debug.LogError("执行失败的有:", True)
- for i in errors:
- oneConfig = 'pack' + str(i)
- Debug.LogError(config.get(oneConfig, 'inPath'))
- Debug.LogError("\t to " + config.get(oneConfig, 'outPath'))
- Debug.Log("")
- Debug.Log("")
- sys.exit(2)
- else:
- Debug.Log("")
- Debug.Log("")
-
-
- def ExecOne(inputPath, outPath, rulePath, resPath,
- keyStorePath, keyAlias, keyStorePwd, keyAliasPwd,
- keySignVer):
- if not os.path.exists(StringFormat.FormatStringPlatform(inputPath)):
- Debug.LogError(inputPath + "不存在")
- return False
- if not outPath or outPath == "":
- Debug.LogError(outPath + "目标目录无效")
- return False
- if not os.path.exists(StringFormat.FormatStringPlatform(rulePath)):
- Debug.LogError(rulePath + "不存在")
- return False
- if not os.path.exists(StringFormat.FormatStringPlatform(resPath)):
- Debug.LogError(resPath + "不存在")
- return False
- if not os.path.exists(StringFormat.FormatStringPlatform(keyStorePath)):
- Debug.LogError(keyStorePath + "不存在")
- return False
- dirOutPath = os.path.dirname(outPath)
- tempOutPath = dirOutPath + TEMP_OUT_PATH
- if os.path.exists(StringFormat.FormatStringPlatform(tempOutPath)):
- Debug.LogError(tempOutPath + "临时解压目录已存在,请先删除")
- return False
- result = ExecOneApk(inputPath, outPath, rulePath, resPath,
- keyStorePath, keyAlias, keyStorePwd, keyAliasPwd,
- keySignVer, tempOutPath)
- if os.path.exists(StringFormat.FormatStringPlatform(tempOutPath)):
- shutil.rmtree(u'' + tempOutPath)
- if os.path.exists(StringFormat.FormatStringPlatform(outPath + ".idsig")):
- os.remove(StringFormat.FormatStringPlatform(outPath + ".idsig"))
- return result
-
- def ExecOneApk(inputPath, outPath, rulePath, resPath,
- keyStorePath, keyAlias, keyStorePwd, keyAliasPwd,
- keySignVer, tempOutPath):
- if keySignVer:
- if isinstance(keySignVer, str):
- if len(keySignVer) > 1:
- if (keySignVer[:1] == 'v' or keySignVer == 'V'):
- keySignVer = keySignVer[1:]
- keySignVer = int(keySignVer)
- else:
- keySignVer = 1
-
- rules = ConfigParser.ConfigParser()
- rules.read(StringFormat.FormatStringPlatform(rulePath))
- covers = rules.items('cover')
- if not covers or len(covers) < 0:
- Debug.LogError(rulePath + ' 规则文件[cover]内容不能为空')
- filters = rules.items('filter')
- try:
- # 解包原始资源
- Debug.Log("解包原始资源")
- Debug.Log("")
- cmds = ["java", "-jar", StringFormat.FormatStringPlatform(APK_TOOL_JAR_PATH),
- "d", StringFormat.FormatStringPlatform(inputPath),
- "-o", StringFormat.FormatStringPlatform(tempOutPath)]
- res = subprocess.Popen(cmds, shell=True, stdout=subprocess.PIPE)
- res.wait()
- if res.returncode != 0:
- Debug.LogError("无法解包 " + inputPath)
- return False
- # 解包新资源
- Debug.Log("解包新资源")
- Debug.Log("")
- cmds = ["java", "-jar", StringFormat.FormatStringPlatform(APK_TOOL_JAR_PATH),
- "d-axml", StringFormat.FormatStringPlatform(resPath),
- "-o", StringFormat.FormatStringPlatform(tempOutPath + "/AndroidManifest-new.xml")]
- res = subprocess.Popen(cmds, shell=False, stdout=subprocess.PIPE)
- res.wait()
- if res.returncode != 0:
- Debug.LogError("无法解包 " + resPath)
- return False
-
- newVersionCode = None
- newVersionName = None
- newLebianVerCode = None
- domTree = minidom.parse(StringFormat.FormatStringPlatform(tempOutPath + "/AndroidManifest-new.xml"))
- root = domTree.documentElement
- newVersionCode = root.getAttribute('platformBuildVersionCode')
- newVersionName = root.getAttribute('platformBuildVersionName')
- applications = root.getElementsByTagName('application')
- for app in applications:
- metadatas = app.getElementsByTagName('meta-data')
- for metadata in metadatas:
- if metadata.getAttribute('android:name') == "LEBIAN_VERCODE":
- newLebianVerCode = metadata.getAttribute('android:value')
- break
- if newLebianVerCode:
- break
- if not newVersionCode or not newVersionName or not newLebianVerCode:
- Debug.LogError("无法识别" + resPath + "里的数据信息")
- return False
- domTree = minidom.parse(StringFormat.FormatStringPlatform(tempOutPath + "/AndroidManifest.xml"))
- root = domTree.documentElement
- # 修改AndroidManifest.xml
- Debug.Log("修改AndroidManifest.xml")
- Debug.Log("")
- Debug.LogSuccess("BuildVersionCode " + root.getAttribute('platformBuildVersionCode') + " -> " + newVersionCode)
- root.setAttribute('platformBuildVersionCode', newVersionCode)
- Debug.LogSuccess("BuildVersionCode " + root.getAttribute('platformBuildVersionName') + " -> " + newVersionName)
- root.setAttribute('platformBuildVersionName', newVersionName)
- applications = root.getElementsByTagName('application')
- for app in applications:
- metadatas = app.getElementsByTagName('meta-data')
- for metadata in metadatas:
- if metadata.getAttribute('android:name') == "LEBIAN_VERCODE":
- Debug.LogSuccess("LEBIAN_VERCODE " + metadata.getAttribute('android:value') + " -> " + newLebianVerCode)
- metadata.setAttribute('android:value', newLebianVerCode)
- newLebianVerCode = None
- break
- if not newLebianVerCode:
- break
- Debug.Log("")
- with open(StringFormat.FormatStringPlatform(tempOutPath + "/AndroidManifest.xml"), "w") as f:
- f.write(domTree.toxml(encoding = "utf-8"))
- os.remove(StringFormat.FormatStringPlatform(tempOutPath + "/AndroidManifest-new.xml"))
- # 更新资源
- Debug.Log("更新资源")
- Debug.Log("")
- with zipfile.ZipFile(StringFormat.FormatStringPlatform(resPath)) as resFile:
- for data in resFile.infolist():
- for cover in covers:
- fileName = data.filename
- if fileName == cover or fileName.startswith(cover):
- if not filters or fileName in filters:
- resFile.extract(data, tempOutPath)
- break
- # 编译新APK
- Debug.Log("编译新APK")
- Debug.Log("")
- cmds = ["java", "-jar", StringFormat.FormatStringPlatform(APK_TOOL_JAR_PATH),
- "b", StringFormat.FormatStringPlatform(tempOutPath)]
- res = subprocess.Popen(cmds, shell=False, stdout=subprocess.PIPE)
- res.wait()
- if res.returncode != 0:
- Debug.LogError("无法编译包 " + tempOutPath)
- return
- # zip对齐apk
- Debug.Log("zip对齐apk")
- Debug.Log("")
- cmds = [StringFormat.FormatStringPlatform(ZIP_ALIGN_PATH),
- "-p", "-f", "4",
- StringFormat.FormatStringPlatform(tempOutPath + "/dist/" + os.path.basename(inputPath)),
- StringFormat.FormatStringPlatform(outPath)]
- res = subprocess.Popen(cmds, shell=False, stdout=subprocess.PIPE)
- res.wait()
- if res.returncode != 0:
- Debug.LogError("无法zip对齐APK " + outPath)
- return
- # 签名apk
- Debug.Log("签名apk")
- Debug.Log("")
- cmds = ["java", "-jar", StringFormat.FormatStringPlatform(APK_SIGNER_JAR_PATH), "sign",
- "--ks", StringFormat.FormatStringPlatform(keyStorePath), "--ks-key-alias", keyAlias,
- "--ks-pass", "pass:" + keyStorePwd, "--key-pass", "pass:" + keyAliasPwd]
- if keySignVer >= 4:
- cmds.append('--v4-signing-enabled')
- cmds.append('true')
- if keySignVer >= 3:
- cmds.append('--v3-signing-enabled')
- cmds.append('true')
- if keySignVer >= 2:
- cmds.append('--v2-signing-enabled')
- cmds.append('true')
- cmds.append('--v1-signing-enabled')
- cmds.append('true')
- cmds.append(StringFormat.FormatStringPlatform(outPath))
- res = subprocess.Popen(cmds, shell=False, stdout=subprocess.PIPE)
- res.wait()
- if res.returncode != 0:
- Debug.LogError("无法签名APK " + outPath)
- return False
- Debug.LogSuccess("执行成功" + inputPath + " -> " + outPath)
- Debug.Log("")
- Debug.Log("")
- return True
- except Exception as exc:
- Debug.LogError(exc)
- Debug.Log("")
- return False
-
- def CMDArgvIsNull(dic, keyName):
- if (not keyName in dic):
- PrintHelp()
- Debug.Log('')
- Debug.Log('')
- Debug.LogError(keyName + ' 不能为空')
- Debug.Log('')
- Debug.Log('')
- return True
- return False
- def Main():
- args = sys.argv[1:]
- dic = {}
- num = len(args)
- for i in range(num):
- arg = StringFormat.FormatStdinString(args[i])
- if arg.startswith('-'):
- if not arg in VALID_CMD_ARGV:
- Debug.LogError(arg + " 不是有效的参数")
- PrintHelp()
- return
- if i + 1 >= num:
- Debug.LogSuccess(arg)
- dic[arg] = ''
- else:
- nextArg = StringFormat.FormatStdinString(args[i + 1])
- if nextArg.startswith('-'):
- Debug.LogSuccess(arg)
- dic[arg] = ''
- else:
- Debug.LogSuccess(arg + " " + nextArg)
- dic[arg] = nextArg
- if len(dic) <= 0:
- PrintHelp()
- return
- if ('-help' in dic):
- PrintHelp()
- return
- if not ValidJava():
- return
- ToolsChmodEXE()
- if ('-configPath' in dic):
- ExecConfig(dic['-configPath'])
- return
- if (CMDArgvIsNull(dic, '-inPath')):
- return
- if (CMDArgvIsNull(dic, '-outPath')):
- return
- if (CMDArgvIsNull(dic, '-rulePath')):
- return
- if (CMDArgvIsNull(dic, '-resPath')):
- return
- if (CMDArgvIsNull(dic, '-keyStorePath')):
- return
- if (CMDArgvIsNull(dic, '-keyAlias')):
- return
- if (CMDArgvIsNull(dic, '-keyStorePwd')):
- return
- if (CMDArgvIsNull(dic, '-keyAliasPwd')):
- return
- Debug.Log('')
- Debug.Log('')
- keySignVer = None
- if ('-keySignVer' in dic):
- keySignVer = dic['-keySignVer']
- ExecOne(dic['-inPath'], dic['-outPath'], dic['-rulePath'], dic['-resPath'],
- dic['-keyStorePath'], dic['-keyAlias'], dic['-keyStorePwd'], dic['-keyAliasPwd'],
- keySignVer)
-
- if __name__ == '__main__':
- try:
- Main()
- except Exception as e:
- Debug.LogError(e)
- Debug.LogError(traceback.format_exc(), True)
- sys.exit(1)
|