def build_apk_gradle(self, rollback_obj, cfg_dir, apk_cfg_info, apk_name): # 最终 apk 文件名格式为:GAMENAME_PKGNAME_APPID_CHANNELID_VERNAME_VERCODE.apk # Logging.debug_msg('修改apk文件名 game_name=%s pkg_name=%s app_id=%s channel_id=%s ver_name=%s ver_code=%s' % (game_name, pkg_name, app_id, channel_id, ver_name, ver_code)) # 修改签名文件 Logging.debug_msg('修改签名文件') keystore_path = utils.flat_path( os.path.join( cfg_dir, apk_cfg_info[PackAPK.CFG_SIGN][PackAPK.CFG_SIGN_FILE])) keystore_pass = apk_cfg_info[PackAPK.CFG_SIGN][ PackAPK.CFG_SIGN_PASSWORD] alias = apk_cfg_info[PackAPK.CFG_SIGN][PackAPK.CFG_SIGN_ALIAS] alias_pass = apk_cfg_info[PackAPK.CFG_SIGN][ PackAPK.CFG_SIGN_ALIAS_PASSWORD] self._modify_gradle_config(rollback_obj, self.output_path, apk_name, keystore_path, keystore_pass, alias, alias_pass) gradle_cmd = "cd %s;gradle clean;gradle aR" % self.proj_androidStudio_path try: utils.run_shell(gradle_cmd) except: Logging.warn_msg('gradle 命令行打包失败') Logging.debug_msg('gradle配置文件修改完成')
def do_build(self): apk_rollback_obj = utils.FileRollback() apk_rollback_obj.record_file( utils.flat_path(os.path.join(self.proj_parth, 'main.js'))) # 编译creator工程 if self.rebuild_creator: self.build_creator_proj() # 删除和替换资源 self.op_creator_res() # 清理temp目录 self.temp_dir = os.path.join(self.root_dir, 'temp') if os.path.isdir(self.temp_dir): shutil.rmtree(self.temp_dir) os.makedirs(self.temp_dir) # 创建mainfest的结构体 self.gen_manifest_obj() # 更新资源目录 self.update_resources() # 生成zip包 self.write_zip_file() apk_rollback_obj.do_rollback() shutil.rmtree(self.temp_dir) Logging.debug_msg('热更包打包完成!\n') Logging.debug_msg('------------------------------------')
def _do_encrypt(self, src, dst_path): Logging.debug_msg('开始资源加密') sys.path.append( os.path.normpath( os.path.join(os.path.dirname(__file__), '../ResEncrypt'))) from ResEncrypt import ResEncrypt encryptor = ResEncrypt(src, dst_path, False, PackAPK.ASSETS_ENCRYPT_EXCLUDE_CFG, True, False) encryptor.do_encrypt() Logging.debug_msg('资源加密结束')
def write_zip_file(self): Logging.debug_msg('正在生成压缩包..') # 创建目录 cur_time_str = datetime.datetime.fromtimestamp( time.time()).strftime('%Y%m%d_%H%M%S') zip_name = '%s_%s_%s_%s.zip' % (self.app_id, self.channel_id, self.batch_info['version'], cur_time_str) output_dir = os.path.join(self.root_dir, 'output', zip_name) utils.zip_folder(self.temp_dir, output_dir) Logging.debug_msg('压缩包生成完成 %s ' % output_dir)
def start(nameStr): Logging.debug_msg("\n") nameLst = [] if nameStr == 'all' or nameStr == 'allmj' or nameStr == 'allpk': nameLst = getAllGames(nameStr) else: nameLst = nameStr.split(',') for name in nameLst: compress_imgs_in_path(name)
def __init__(self, args): self.src_file = utils.flat_path(args.src_file) if not os.path.isfile(self.src_file): raise_known_error('文件 %s 不存在' % self.src_file, KnownError.ERROR_PATH_NOT_FOUND) if args.dst_file: self.dst_file = utils.flat_path(args.dst_file) else: name, ext = os.path.splitext(self.src_file) self.dst_file = '%s_new%s' % (name, ext) Logging.debug_msg("原始文件路径:%s" % self.src_file) Logging.debug_msg("输出文件路径:%s" % self.dst_file)
def build_creator_proj(self): Logging.debug_msg('开始构建creator项目') try: if sys.platform == "win32": utils.run_shell( 'cd /d %s & CocosCreator.exe --path %s --build "platform=android;debug=false"' % (self.creator_exe_path, self.creator_proj_path)) else: creator_path = '/Applications/CocosCreator.app/Contents/MacOS' utils.run_shell( 'pushd "%s";CocosCreator --path %s --build "platform=android;debug=false";popd' % (creator_path, self.creator_proj_path)) except Exception as e: raise_known_error("Creator 项目命令行构建失败 msg=%s" % str(e)) Logging.debug_msg('构建creator项目完成')
def traverseDir(absDir): #遍历当前目录以及递归的子目录,找到所有的png图片 # test = "db://assets/resources/common/textures/bl_vip_10.png/bl_vip_10" # print(test.find("/common/")) count = 0 compress_count = 0 for (parent, dirs, files) in os.walk(absDir): for f in files: full_path = os.path.join(parent, f) rel_path = os.path.relpath(full_path, absDir) rel_path = rel_path.replace("\\", "/") ext = os.path.splitext(rel_path)[1] if ext == ".png": count += 1 if needCompress(rel_path): compress_count += 1 processPNG(full_path) Logging.debug_msg("Total image count : %d. Compress count : %d" % (count, compress_count))
def __init__(self, args): self.batch_cfg_file = utils.flat_path(args.proj_cfg) if not os.path.isfile(self.batch_cfg_file): raise_known_error("文件 %s 不存在" % self.batch_cfg_file, KnownError.ERROR_PATH_NOT_FOUND) self.root_dir = os.path.dirname(self.batch_cfg_file) cur_time_str = datetime.datetime.fromtimestamp( time.time()).strftime('%Y%m%d') self.output_path = os.path.join(self.root_dir, 'output', cur_time_str) #配置文件数据 self.batch_info = self.parse_json(self.batch_cfg_file) if not self.batch_info: raise_known_error('解析文件 %s 失败' % self.batch_cfg_file, KnownError.ERROR_PARSE_FILE) Logging.debug_msg('使用配置文件 : %s' % self.batch_cfg_file) Logging.debug_msg('----------------\n')
def build_apk_gradle(self, rollback_obj, cfg_dir, apk_cfg_info, apk_name): # 最终 apk 文件名格式为:GAMENAME_PKGNAME_APPID_CHANNELID_VERNAME_VERCODE.apk # Logging.debug_msg('修改apk文件名 game_name=%s pkg_name=%s app_id=%s channel_id=%s ver_name=%s ver_code=%s' % (game_name, pkg_name, app_id, channel_id, ver_name, ver_code)) # 修改签名文件 Logging.debug_msg('修改签名文件') self.setGradleConfig(rollback_obj, cfg_dir, apk_cfg_info, apk_name) try: if sys.platform == "win32": utils.run_shell( 'cd /d %s & set ANDROID_HOME="%s" & gradlew clean & gradlew aR --stacktrace' % (self.proj_android_path, self.sdk_root)) else: utils.run_shell( 'pushd "%s";export ANDROID_HOME="%s";./gradlew clean;./gradlew aR --stacktrace;popd' % (self.proj_android_path, self.sdk_root)) except Exception as e: raise_known_error("gradle 命令行打包失败 msg=%s" % str(e)) Logging.debug_msg('gradle配置文件修改完成')
def __init__(self, args): # 配置文件 self.batch_cfg_file = utils.flat_path(args.proj_cfg) if not os.path.isfile(self.batch_cfg_file): raise_known_error("文件 %s 不存在" % self.batch_cfg_file, KnownError.ERROR_PATH_NOT_FOUND) self.root_dir = os.path.dirname(self.batch_cfg_file) self.batch_info = self._parse_json(self.batch_cfg_file) if not self.batch_info: raise_known_error('解析文件 %s 失败' % self.batch_cfg_file, KnownError.ERROR_PARSE_FILE) self.app_id = self.batch_info['appid'] self.channel_id = self.batch_info['channel'] self.proj_parth = utils.flat_path( os.path.join(self.root_dir, self.batch_info['proj_root'])) self.pack_parth = utils.flat_path( os.path.join(self.root_dir, self.batch_info['pack_root'])) # 是否需要重编creator self.rebuild_creator = args.rebuild_creator if self.rebuild_creator: # 检查Creator的安装目录 self.creator_exe_path = utils.check_environment_variable( 'CREATOR_PATH') if not os.path.isdir(self.creator_exe_path): raise_known_error("环境变量 CREATOR_PATH 未设置") self.creator_proj_path = utils.flat_path( os.path.join(self.proj_parth, '../../')) self.encrypt_res = args.encrypt_res Logging.debug_msg('是否重新编译creator工程 : %s' % self.rebuild_creator) Logging.debug_msg('是否加密图片资源 : %s' % self.encrypt_res) Logging.debug_msg('------------------------------------\n')
def op_creator_res(self): # 删除本地配置文件 CustomScript.js custom_file_path = utils.flat_path( os.path.join(self.proj_parth, 'src/assets/scripts')) self.del_js_file(custom_file_path, 'CustomScript') # #压缩图片资源 # exclude_dict = { # "exclude_files": [ # # "*_s9.png" # ], # } # if not self.no_encrypt: # pq = png_quant.PngQuant(self.res_root_path, exclude_dict) # pq.compress_images_in_path() # 加密图片 if self.encrypt_res: Logging.debug_msg('开始加密图片资源') res_root_path = utils.flat_path( os.path.join(self.proj_parth, 'res')) encrytPng.traverseDir(res_root_path) Logging.debug_msg('开始图片资源加密完成')
def do_build(self): try: for app_id in self.batch_info.keys(): app_id = utils.non_unicode_str(app_id) channels = self.batch_info[app_id] for channel_id in channels: Logging.debug_msg('----------------') apk_rollback_obj = utils.FileRollback() try: channel_str = self.get_string(channel_id) self.build_result['%s_%s' % (app_id, channel_str)] = '' self.build_one_apk(app_id, channel_str, apk_rollback_obj) except: Logging.warn_msg('打包失败') finally: if not self.no_rollback: apk_rollback_obj.do_rollback() Logging.debug_msg('----------------\n') Logging.debug_msg('\n打包结果汇总 :') for key in self.build_result.keys(): ids = key.split('_') value = self.build_result[key] if value and os.path.isfile(utils.get_sys_encode_str(value)): Logging.debug_msg( 'APP_ID : %s, CHANNEL_ID : %s。打包成功。apk 路径 : %s' % (ids[0], ids[1], value)) else: Logging.debug_msg('APP_ID : %s, CHANNEL_ID : %s。打包失败。' % (ids[0], ids[1])) except Exception: raise finally: if os.path.isdir(self.temp_dir): shutil.rmtree(self.temp_dir)
def __init__(self, args): self.batch_cfg_file = utils.flat_path(args.proj_cfg) if not os.path.isfile(self.batch_cfg_file): raise_known_error("文件 %s 不存在" % self.batch_cfg_file, KnownError.ERROR_PATH_NOT_FOUND) self.no_rollback = args.no_rollback self.root_dir = os.path.dirname(self.batch_cfg_file) cur_time_str = datetime.datetime.fromtimestamp( time.time()).strftime('%Y%m%d_%H%M%S') self.output_path = os.path.join(self.root_dir, 'output', cur_time_str) #配置文件数据 self.batch_info = self._parse_json(self.batch_cfg_file) if not self.batch_info: raise_known_error('解析文件 %s 失败' % self.batch_cfg_file, KnownError.ERROR_PARSE_FILE) # 检查 android sdk 环境变量 self.sdk_root = utils.flat_path( utils.check_environment_variable('ANDROID_SDK_ROOT')) self.build_result = {} cur_dir = os.path.dirname(__file__) self.proj_parth = utils.flat_path( os.path.join(cur_dir, '../../../jsb-default')) self.proj_android_path = utils.flat_path( os.path.join(self.proj_parth, 'project_android_20')) if not os.path.isdir(self.proj_android_path): raise_known_error("未找到 Android 工程文件夹 %s" % self.proj_android_path) self.android_manifest = os.path.join( self.proj_android_path, 'launcher/src/main/AndroidManifest.xml') self.java_files = [] for parent, dirs, files in os.walk( utils.flat_path( os.path.join(self.proj_android_path, 'unityLibrary/src/main/java'))): for f in files: filename, ext = os.path.splitext(f) if ext.lower() == '.java': self.java_files.append(os.path.join(parent, f)) Logging.debug_msg('使用配置文件 : %s' % self.batch_cfg_file) Logging.debug_msg('是否禁用还原 : %s' % self.no_rollback) Logging.debug_msg('Android 工程路径 : %s' % self.proj_android_path) Logging.debug_msg('----------------\n')
def needCompress(key): global PngMap # folder, basename = os.path.split(key) # pattern = r"([a-f0-9\-]+)\.[a-f0-9\-]{5}\.png" # m = re.match(pattern, basename) # if not m: # Logging.warn_msg("The file path %s is not in right format." % key) # return False # # checkKey = os.path.join(folder, "%s.png" % m.group(1)) checkKey = key.replace('\\', '/') if PngMap.has_key(checkKey): srcPath = os.path.dirname(PngMap[checkKey]) srcPath = srcPath.lstrip("db://assets/") base, ext = os.path.splitext(srcPath) if (ext == ".plist"): srcPath = base + '.png' # 检查不需要压缩的文件夹 unDirs = Uncompress.get('dirs', []) for d in unDirs: if srcPath.startswith(d): Logging.warn_msg("%s in uncompress dir : %s" % (key, d)) return False # 检查不需要压缩的文件 unFiles = Uncompress.get('files', []) for f in unFiles: if srcPath == f: Logging.warn_msg("%s is uncompress file %s" % (key, f)) return False Logging.debug_msg("Compress image : %s (%s)" % (key, srcPath)) return True else: Logging.warn_msg("%s is not found in PngMap.json" % checkKey) return False
def gen_manifest_obj(self): Logging.debug_msg('正在生成manifest结构..') self.manifest_obj = {} self.manifest_obj["version"] = self.batch_info['version'] self.manifest_obj["packageUrl"] = '' self.manifest_obj["remoteManifestUrl"] = '' self.manifest_obj["remoteVersionUrl"] = '' hotUpdateUrl = self.batch_info['url'] if len(hotUpdateUrl) > 1: self.manifest_obj["packageUrl"] = hotUpdateUrl + '' self.manifest_obj[ "remoteManifestUrl"] = hotUpdateUrl + 'project.manifest' self.manifest_obj[ "remoteVersionUrl"] = hotUpdateUrl + 'version.manifest' # 先创建version.manifest version_manifest = utils.flat_path( os.path.join(self.temp_dir, 'version.manifest')) file_m = open(version_manifest, "wb") file_m.writelines(json.dumps(self.manifest_obj)) file_m.close() self.manifest_obj["searchPaths"] = [] self.manifest_obj["assets"] = {}
def update_resources(self): Logging.debug_msg('正在拷贝资源到tmp目录..') # 拷贝资源到tmp目录 for res in self.batch_info['include']: if not res: continue copy_cfg = {'from': res, 'to': res, 'exclude': ['**/.DS_Store']} excopy.copy_files_with_config(copy_cfg, self.proj_parth, self.temp_dir) # 先删除CfgPackage.js或CfgPackage.jsc scripts_path = utils.flat_path( os.path.join(self.temp_dir, 'src/assets/scripts')) self.del_js_file(scripts_path, 'CfgPackage') self.del_js_file(scripts_path, 'manifest') # 替换CfgPackage.js文件 pack_cfg_path = utils.flat_path( os.path.join(self.pack_parth, self.app_id, self.channel_id)) copy_cfg = { 'from': 'cfg', 'to': 'src/assets/scripts', 'exclude': ['**/.DS_Store'] } excopy.copy_files_with_config(copy_cfg, pack_cfg_path, self.temp_dir) Logging.debug_msg('正在生成资源列表的md5信息..') # 生成资源列表的md5信息 assetsObj = self.manifest_obj['assets'] self.get_file_path_md5(self.temp_dir, assetsObj) # 替换掉文件 manifest.js tmp_str = "(function() { window.CustomManifestAssets = holdString})();" tmp_str = tmp_str.replace("holdString", json.dumps(assetsObj)) src_path = utils.flat_path(os.path.join(self.temp_dir, 'src')) manifest_js_path = utils.flat_path( os.path.join(src_path, 'assets/scripts/manifest.js')) file_m = open(manifest_js_path, "wb") file_m.writelines(tmp_str) file_m.close() # 重新计算manifest.js的md5值 rel_path = 'src/assets/scripts/manifest.js' assetsObj[rel_path] = { 'size': os.path.getsize(manifest_js_path), 'md5': self.get_file_md5(manifest_js_path) } Logging.debug_msg('生成project.manifest文件..') # 创建project.manifest version_manifest = utils.flat_path( os.path.join(self.temp_dir, 'project.manifest')) file_m = open(version_manifest, "wb") file_m.writelines(json.dumps(self.manifest_obj)) file_m.close()
def build_creator_proj(self): Logging.debug_msg('开始构建creator项目') Logging.debug_msg(self.creator_proj_parth) creator_exe_path = utils.check_environment_variable('CREATOR_PATH') try: if sys.platform == "win32": utils.run_shell( 'cd /d %s & CocosCreator.exe --path %s --build "platform=wechatgame;debug=false"' % (creator_exe_path, self.creator_proj_parth)) else: creator_path = '/Applications/CocosCreator.app/Contents/MacOS/' utils.run_shell( '%sCocosCreator --path %s --build "platform=wechatgame;debug=false"' % (creator_path, self.creator_proj_parth)) except Exception as e: raise_known_error("Creator 项目命令行构建失败 msg=%s" % str(e)) Logging.debug_msg('构建creator项目完成')
def do_build(self): #各个游戏,各个渠道 for game in self.batch_info.keys(): game = utils.non_unicode_str(game) channels = self.batch_info[game] for channel_id in channels: Logging.debug_msg('----------------' + channel_id) try: channel_str = utils.non_unicode_str(channel_id) apk_cfg_file = os.path.join(self.root_dir, game, channel_str, 'package.json') if not os.path.isfile(apk_cfg_file): Logging.warn_msg('未找到 %s 文件' % apk_cfg_file) return pack_cfg_info = self.parse_json(apk_cfg_file) if pack_cfg_info is None: Logging.warn_msg('解析文件 %s 出错' % apk_cfg_file) return strResPath = utils.non_unicode_str( pack_cfg_info['res_path']) res_path = utils.flat_path( os.path.join(self.root_dir, game, channel_str, strResPath)) strTargetpath = utils.non_unicode_str( pack_cfg_info['target_path']) target_path = utils.flat_path( os.path.join(self.root_dir, strTargetpath)) copy_cfg = { 'from': '.', 'to': '.', 'exclude': ['**/.DS_Store'] } excopy.copy_files_with_config(copy_cfg, res_path, target_path) except: Logging.warn_msg('资源拷贝失败') finally: Logging.debug_msg('finally :') Logging.debug_msg('----------------\n')
def build_creator_proj(self): Logging.debug_msg('开始构建creator项目') Logging.debug_msg(self.creator_proj_path) try: # 删除之前构建生成的文件夹 if os.path.isdir(self.proj_parth): shutil.rmtree(self.proj_parth) json_file = os.path.join(self.creator_proj_path, "settings/wechatgame.json") loads = self.readJson(json_file) loads["startSceneAssetBundle"] = False self.writeJson(json_file, loads) # https://docs.cocos.com/creator/manual/en/publish/publish-in-command-line.html buildoptions = ";".join([ "platform=wechatgame", "buildPath=build", "debug=false", "sourceMaps=false", "md5Cache=true", "mainCompressionType=merge_all_json", "mainIsRemote=false" ]) paramFmt = '--path {path} --build "{options}"' params = paramFmt.format(path=self.creator_proj_path, options=buildoptions) if sys.platform == "win32": creator_exe_path = utils.check_environment_variable( 'CREATOR_PATH') cmdline = 'cd /d "%s" & CocosCreator.exe %s' % ( creator_exe_path, params) utils.run_shell(cmdline) else: creator_path = '/Applications/CocosCreator/Creator/2.4.3/CocosCreator.app/Contents/MacOS/CocosCreator' utils.run_shell('%s %s' % (creator_path, params)) except Exception as e: raise_known_error("Creator 项目命令行构建失败 msg=%s" % str(e)) Logging.debug_msg('构建creator项目完成')
def start(absDir, pngMapPath): #遍历当前目录以及递归的子目录,找到所有的png图片 global ext_path global PngMap global Uncompress Logging.debug_msg("----- Handle image compress begin. ----") if utils.os_is_win32(): ext_path = utils.flat_path( os.path.join(os.path.dirname(__file__), "../../png_quant/bin/win32/pngquant")) else: ext_path = utils.flat_path( os.path.join(os.path.dirname(__file__), "../../png_quant/bin/mac/pngquant")) Logging.debug_msg("Tool path : " + ext_path) absDir = utils.flat_path(absDir) Logging.debug_msg("Handle images path : " + absDir) Logging.debug_msg("PngMap.json path : " + pngMapPath) try: f = open(pngMapPath) PngMap = json.load(f) f.close() except: pass #print(PngMap['res/raw-assets/6a/6a811324-26ea-4e78-b238-4509160c26ad.png']) json_file = utils.flat_path( os.path.join(os.path.dirname(__file__), "uncompress.json")) Logging.debug_msg("uncompress.json path :" + json_file) try: f = open(json_file) Uncompress = json.load(f) f.close() except: pass traverseDir(absDir) Logging.debug_msg("----- Handle image compress end. ----")
def do_scan(self): # 获取需要检测的文件路径字符串 types_count = {} Logging.debug_msg("---- start scanning ----") for folder in RES_FOLDERS: check_folder = utils.flat_path( os.path.join(self.res_root_path, folder)) for parent, dirs, files in os.walk(check_folder): if self._check_excludes(parent): continue for f in files: base_name, ext = os.path.splitext(f) full_path = os.path.join(parent, f) relative_path = os.path.relpath(full_path, check_folder) if ext in RES_TYPES: # 统计资源数量 if types_count.has_key(ext): types_count[ext] += 1 else: types_count[ext] = 1 if ext == '.plist': if not self._check_excludes(full_path): self._parse_plist(full_path, relative_path) else: find_path = relative_path if ext == '.png': # png 可能是合图或者 fnt 字体,跳过 plist_path = os.path.join( parent, base_name + '.plist') fnt_path = os.path.join( parent, base_name + '.fnt') if os.path.isfile( plist_path) or os.path.isfile( fnt_path): find_path = None if find_path: self._do_add_check_files(find_path, full_path) for folder in STUDIO_PROJ_FOLDERS: studio_folder = utils.flat_path( os.path.join(self.res_root_path, folder)) for parent, dirs, files in os.walk(studio_folder): for f in files: base_name, ext = os.path.splitext(f) full_path = os.path.join(parent, f) if ext == '.csd': if types_count.has_key(ext): types_count[ext] += 1 else: types_count[ext] = 1 relative_path = os.path.relpath( full_path, studio_folder) relative_js = os.path.join( os.path.dirname(relative_path), base_name + '.js') js_path = os.path.join(self.res_root_path, PUBLISH_RES_FOLDER, relative_js) if not os.path.isfile(js_path): # 发布的 js 文件已经不存在了,不需要检查 continue csd_js_file = base_name + '.js' self._do_add_check_files(csd_js_file, full_path) # 还需要加入 a.b.c 的检查 require_key = os.path.join( os.path.dirname(relative_path), base_name) require_key = require_key.replace('/', '.') require_key = require_key.replace('\\', '.') self._do_add_check_files(require_key, full_path) Logging.debug_msg("\n") for ext in types_count: Logging.debug_msg("%s files: %d" % (ext, types_count[ext])) Logging.debug_msg("---- scan finished %d ----\n" % len(self.check_files)) # Logging.debug_msg('need check files (%d):' % len(check_files)) # for key in check_files: # Logging.debug_msg('%s : %s' % (key, check_files[key])) # 读取需要检查的 js 文件内容 js_files_info = {} for folder in CHECK_FOLDERS: check_folder = utils.flat_path( os.path.join(self.res_root_path, folder)) for parent, dirs, files in os.walk(check_folder): if self._check_excludes(parent): continue for f in files: base_name, ext = os.path.splitext(f) full_path = os.path.join(parent, f) if ext in CHECK_FILE_TYPES: # 读取文件内容 f = open(full_path) content = f.read() f.close() js_files_info[full_path] = content # 进行检查 self.unused = [] self.used = [] for key in self.check_files: tip_str = self.check_files[key] if tip_str in self.used: # 之前已经找到使用的地方了,直接跳过 continue used = False for content_key in js_files_info: if self._check_excludes(content_key): continue content = js_files_info[content_key] if content.find(key) >= 0: used = True break if used: self.used.append(tip_str) if tip_str in self.unused: # 从 unused 中移除 self.unused.remove(tip_str) else: if not self._is_in_unused_list(tip_str): # 之前未找到过,记录下来 self.unused.append(tip_str) # 输出未使用的文件 Logging.debug_msg("unused file(%d):" % len(self.unused)) unused_temp_path = utils.flat_path('~/Downloads/unused_files') if os.path.isdir(unused_temp_path): shutil.rmtree(unused_temp_path) os.makedirs(unused_temp_path) self.unused = sorted(self.unused) cur_dir = os.path.dirname(__file__) path = os.path.join(cur_dir, "unused_files.txt") unused_files = open(path, "w") for f in self.unused: unused_files.write(f + "\n") unused_files.close() if self.delete: self._do_delete()
def build_one_apk(self, app_id, channel_id, rollback_obj): apk_cfg_file = os.path.join(self.root_dir, app_id, channel_id, 'package.json') if not os.path.isfile(apk_cfg_file): Logging.warn_msg('未找到 %s 文件,打包失败1' % apk_cfg_file) return Logging.debug_msg('开始使用配置文件 %s 打包' % apk_cfg_file) apk_cfg_info = self._parse_json(apk_cfg_file) if apk_cfg_info is None: Logging.warn_msg('解析文件 %s 出错,打包失败2' % apk_cfg_file) return cfg_dir = os.path.dirname(apk_cfg_file) check_ret = self._check_cfg_info(apk_cfg_info, cfg_dir) if check_ret: Logging.warn_msg(check_ret + ',打包失败5') return # gradle需要修改 package.json 配置文件 rollback_obj.record_file(apk_cfg_file) game_name = utils.non_unicode_str(apk_cfg_info[PackAPK.CFG_GAME_NAME]) pkg_name = utils.non_unicode_str(apk_cfg_info[PackAPK.CFG_PKG_NAME]) ver_name = utils.non_unicode_str( apk_cfg_info[PackAPK.CFG_VERSION_NAME]) ver_code = utils.non_unicode_str( apk_cfg_info[PackAPK.CFG_VERSION_CODE]) app_scheme = utils.non_unicode_str( apk_cfg_info[PackAPK.CFG_APP_SCHEME]) # 修改 strings.xml self._modify_strings_xml(game_name, app_scheme) # 修改渠道号 isThirdPart = apk_cfg_info.get(PackAPK.CFG_THIRD_PART, False) self._modify_apihelper(apk_cfg_info[PackAPK.CFG_CHANNEL_ID], isThirdPart, rollback_obj) # 修改 java 文件中 R 的包名 self._modify_java_files(pkg_name, rollback_obj) # 修改 AndroidManifest.xml self._modify_manifest(pkg_name, ver_name, ver_code, rollback_obj) # 替换 res self._modify_res(apk_cfg_info, cfg_dir, rollback_obj) # 替换 libs self._modify_libs(apk_cfg_info, cfg_dir, rollback_obj) # 最终 apk 文件名格式为:GAMENAME_PKGNAME_APPID_CHANNELID_VERNAME_VERCODE.apk Logging.debug_msg('生成apk文件名') name_infos = [ game_name, pkg_name, app_id, channel_id, ver_name, ver_code ] # 文件名增加时间戳 Logging.debug_msg('文件名增加时间戳') cur_time_str = datetime.datetime.fromtimestamp( time.time()).strftime('%Y%m%d_%H%M%S') name_infos.append(cur_time_str) apk_name = '_'.join(name_infos) + '.apk' out_file_path = os.path.join(self.output_path, apk_name) if not os.path.isdir(self.output_path): os.makedirs(self.output_path) # 进行 gradle 打包 Logging.debug_msg('进行 gradle 打包') try: self.build_apk_gradle(rollback_obj, cfg_dir, apk_cfg_info, apk_name) self.build_result['%s_%s' % (app_id, channel_id)] = out_file_path except: print 'traceback.format_exc():\n%s' % traceback.format_exc() return
def do_build(self): #各个渠道的打包 for app_id in self.batch_info.keys(): app_id = utils.non_unicode_str(app_id) channels = self.batch_info[app_id] for channel_id in channels: Logging.debug_msg('----------------') apk_rollback_obj = utils.FileRollback() try: channel_str = self.get_string(channel_id) self.build_result['%s_%s' % (app_id, channel_str)] = '' self.build_one_apk(app_id, channel_str, apk_rollback_obj) except: Logging.warn_msg('打包失败') finally: Logging.debug_msg('finally :') if not self.no_rollback: apk_rollback_obj.do_rollback() Logging.debug_msg('----------------\n') Logging.debug_msg('\n打包结果汇总 :') for key in self.build_result.keys(): ids = key.split('_') value = self.build_result[key] if value and os.path.isfile(utils.get_sys_encode_str(value)): Logging.debug_msg( 'APP_ID : %s, CHANNEL_ID : %s。打包成功。apk 路径 : %s' % (ids[0], ids[1], value)) else: Logging.debug_msg('APP_ID : %s, CHANNEL_ID : %s。打包失败。' % (ids[0], ids[1])) #打开目录 os.system("start explorer %s" % self.output_path)
def __init__(self, args): self._check_env() self.batch_cfg_file = utils.flat_path(args.proj_cfg) if not os.path.isfile(self.batch_cfg_file): raise_known_error("文件 %s 不存在" % self.batch_cfg_file, KnownError.ERROR_PATH_NOT_FOUND) self.no_rollback = args.no_rollback self.no_encrypt = args.no_encrypt self.build_gradle = args.build_gradle self.root_dir = os.path.dirname(self.batch_cfg_file) self.temp_dir = os.path.join(self.root_dir, 'temp') cur_time_str = datetime.datetime.fromtimestamp( time.time()).strftime('%Y%m%d_%H%M%S') self.output_path = os.path.join(self.root_dir, 'output', cur_time_str) # 保存打包文件夹名称时间戳 self.packageTimestamp = cur_time_str self.batch_info = self._parse_json(self.batch_cfg_file) if not self.batch_info: raise_known_error('解析文件 %s 失败' % self.batch_cfg_file, KnownError.ERROR_PARSE_FILE) cur_dir = os.path.dirname(__file__) self.proj_android_path = utils.flat_path( os.path.join(cur_dir, '../../frameworks/runtime-src/proj.android')) self.proj_androidStudio_path = utils.flat_path( os.path.join(cur_dir, '../../frameworks/runtime-src/proj.android-studio')) self.res_root_path = utils.flat_path(os.path.join(cur_dir, '../../')) self.games_root_path = utils.flat_path( os.path.join(self.res_root_path, '../games')) if not os.path.isdir(self.proj_android_path): raise_known_error("未找到 Android 工程文件夹 %s" % self.proj_android_path) self.library_android_path = utils.flat_path( os.path.join(cur_dir, '../../frameworks/runtime-src/library.android')) self.library_manifest = os.path.join(self.library_android_path, 'AndroidManifest.xml') self.manifest = os.path.join(self.proj_android_path, 'AndroidManifest.xml') self.strings = os.path.join(self.proj_android_path, 'res/values/strings.xml') self.build_xml = os.path.join(self.proj_android_path, 'build.xml') self.proj_name = self._get_proj_name() self.brand = args.brand self.java_files = [] for parent, dirs, files in os.walk( utils.flat_path(os.path.join(self.proj_android_path, 'src'))): for f in files: filename, ext = os.path.splitext(f) if ext.lower() == '.java': self.java_files.append(os.path.join(parent, f)) Logging.debug_msg('使用配置文件 : %s' % self.batch_cfg_file) Logging.debug_msg('是否禁用加密 : %s' % self.no_encrypt) Logging.debug_msg('是否禁用还原 : %s' % self.no_rollback) Logging.debug_msg('Android 工程路径 : %s' % self.proj_android_path) Logging.debug_msg('----------------\n') self.build_result = {}
def build_one_apk(self, app_id, channel_id, rollback_obj): apk_cfg_file = os.path.join(self.root_dir, app_id, channel_id, 'package.json') if not os.path.isfile(apk_cfg_file): Logging.warn_msg('未找到 %s 文件,打包失败' % apk_cfg_file) return Logging.debug_msg('开始使用配置文件 %s 打包' % apk_cfg_file) apk_cfg_info = self._parse_json(apk_cfg_file) if apk_cfg_info is None: Logging.warn_msg('解析文件 %s 出错,打包失败' % apk_cfg_file) return cfg_dir = os.path.dirname(apk_cfg_file) check_gt_ret = self._check_gt_info(apk_cfg_info, cfg_dir) if check_gt_ret: Logging.warn_msg(check_gt_ret + ', 打包失败') return check_ret = self._check_cfg_info(apk_cfg_info, cfg_dir) if check_ret: Logging.warn_msg(check_ret + ',打包失败') return # 备份文件和文件夹 rollback_obj.record_file(utils.flat_path(self.library_manifest)) rollback_obj.record_file(utils.flat_path(self.manifest)) rollback_obj.record_folder(os.path.join(self.proj_android_path, 'res')) game_name = utils.non_unicode_str(apk_cfg_info[PackAPK.CFG_GAME_NAME]) pkg_name = utils.non_unicode_str(apk_cfg_info[PackAPK.CFG_PKG_NAME]) gaode_key = utils.non_unicode_str(apk_cfg_info[PackAPK.CFG_GAODE_KEY]) ver_name = utils.non_unicode_str( apk_cfg_info[PackAPK.CFG_VERSION_NAME]) ver_code = utils.non_unicode_str( apk_cfg_info[PackAPK.CFG_VERSION_CODE]) app_scheme = utils.non_unicode_str( apk_cfg_info[PackAPK.CFG_APP_SCHEME]) need_remove_agora = True # Android 去掉视频 SDK apk_cfg_info.get(PackAPK.CFG_NO_AGORA, False) # 修改 java 文件中 R 的包名 self._modify_java_files(pkg_name, rollback_obj) # 对需要修改包名的文件进行包名替换 self._change_pkg_name(pkg_name, rollback_obj) # 修改 bugly appid if apk_cfg_info.has_key(PackAPK.CFG_BUGLY_ID): bugly_appid = utils.non_unicode_str( apk_cfg_info[PackAPK.CFG_BUGLY_ID]) else: bugly_appid = "" if len(bugly_appid) > 0: self._modify_bugly(self.library_manifest, bugly_appid, channel_id) # 修改高德 key if len(gaode_key) > 0: self._modify_gaode_key(self.library_manifest, gaode_key) # 修改 Agora id if apk_cfg_info.has_key(PackAPK.CFG_AGORA_ID): self._modify_agora_id(self.library_manifest, apk_cfg_info[PackAPK.CFG_AGORA_ID]) # 修改 AndroidManifest.xml self._modify_manifest(pkg_name, ver_name, ver_code) # 替换 java res for p in apk_cfg_info[PackAPK.CFG_JAVA_RES].split(','): java_res_path = utils.flat_path(os.path.join(cfg_dir, p.strip())) copy_cfg = {'from': '.', 'to': 'res', 'exclude': ['**/.DS_Store']} excopy.copy_files_with_config(copy_cfg, java_res_path, self.proj_android_path) # 替换 个推通知图标push.png # isTurnOffPush = apk_cfg_info.get(PackAPK.CFG_TURNOFF_PUSH, None) # if isTurnOffPush != "1": push_res_path = utils.flat_path(os.path.join(cfg_dir, "../pushRes")) proj_getui_path = utils.flat_path( os.path.join(self.proj_android_path, '../library.pushgetui.android')) if not os.path.isdir(proj_getui_path): raise_known_error("未找到 getui 工程文件夹 %s" % proj_getui_path) copy_cfg = {'from': '.', 'to': 'res', 'exclude': ['**/.DS_Store']} rollback_obj.record_folder(os.path.join(proj_getui_path, "res")) if os.path.isdir(push_res_path): excopy.copy_files_with_config(copy_cfg, push_res_path, proj_getui_path) else: # raise_known_error('文件夹 %s 不存在,放置个推通知图标pushRes/drawable-xxhdpi/push.png|push_small.png' % push_res_path, KnownError.ERROR_PATH_NOT_FOUND) res_path = utils.flat_path( os.path.join(self.proj_android_path, "res")) pushSmallSrc_path = utils.flat_path( os.path.join(res_path, "mipmap-hdpi/ic_launcher.png")) pushSrc_path = utils.flat_path( os.path.join(res_path, "mipmap-xxxhdpi/ic_launcher.png")) pushDes_path = utils.flat_path( os.path.join(proj_getui_path, "res/drawable-xxhdpi/push.png")) pushSmallDes_path = utils.flat_path( os.path.join(proj_getui_path, "res/drawable-xxhdpi/push_small.png")) # excopy.copy_files_with_config(copy_cfg, push_res_path, proj_getui_path) shutil.copyfile(pushSrc_path, pushDes_path) shutil.copyfile(pushSmallSrc_path, pushSmallDes_path) Logging.debug_msg('个推通知图标替换完成') # 修改 strings.xml self._modify_strings_xml(game_name, app_scheme) # 获取并清理临时的 assets 文件夹 temp_assets_path = os.path.join(self.temp_dir, '%s_%s_assets' % (app_id, channel_id)) if os.path.isdir(temp_assets_path): shutil.rmtree(temp_assets_path) Logging.debug_msg('开始处理资源文件') # 拷贝大厅的资源文件 for p in PackAPK.DEFAULT_HALL_DIRS: copy_cfg = { 'from': p, 'to': p, 'exclude': ['**/.DS_Store', 'LuaDebugjit.lua'] } excopy.copy_files_with_config(copy_cfg, self.res_root_path, temp_assets_path) # 拷贝游戏通用组件的各个部分 game_common_parts = apk_cfg_info.get(PackAPK.CFG_GAME_COMMON_PARTS, None) self._copy_game_common_parts(game_common_parts, temp_assets_path) # 拷贝指定的游戏文件 games_dirs = [] games = apk_cfg_info.get(PackAPK.CFG_GAMES, None) if isinstance(games, list) and len(games) > 0: games_dirs.extend(games) if len(games_dirs) > 0: if not os.path.isdir(self.games_root_path): raise_known_error('文件夹 %s 不存在' % self.games_root_path, KnownError.ERROR_PATH_NOT_FOUND) for game in games_dirs: copy_cfg = { 'from': '%s' % game, 'to': 'games/%s' % game, 'exclude': ['**/.DS_Store', '*.git'] } excopy.copy_files_with_config(copy_cfg, self.games_root_path, temp_assets_path) # 替换游戏配置文件 for p in apk_cfg_info[PackAPK.CFG_FILES].split(','): cfg_path = utils.flat_path(os.path.join(cfg_dir, p.strip())) copy_cfg = {'from': '.', 'to': 'src', 'exclude': ['**/.DS_Store']} excopy.copy_files_with_config(copy_cfg, cfg_path, temp_assets_path) # cfg_package.lua文件末尾写入打包文件夹名称时间戳 tmpFile = open(os.path.join(temp_assets_path, "src/cfg_package.lua"), 'a') tmpFile.write('\nPACKAGE_TIMESTAMP=' + '\"' + self.packageTimestamp + '\"') tmpFile.close() # 替换 res 文件夹 if apk_cfg_info.has_key(PackAPK.CFG_RES_FILES): for p in apk_cfg_info[PackAPK.CFG_RES_FILES].split(','): res_path = utils.flat_path(os.path.join(cfg_dir, p.strip())) copy_cfg = { 'from': '.', 'to': 'res', 'exclude': ['**/.DS_Store'] } excopy.copy_files_with_config(copy_cfg, res_path, temp_assets_path) # 删除不需要的品牌资源文件夹 for brand in utils.get_support_brands(): brand_path = utils.flat_path( os.path.join(temp_assets_path, 'res', brand)) if brand != self.brand and os.path.isdir(brand_path): shutil.rmtree(brand_path) # 删除不属于该地区的地图资源文件 if apk_cfg_info.has_key(PackAPK.CFG_REGION) and apk_cfg_info[ PackAPK.CFG_REGION] != 'all': region_list = [] new_list = apk_cfg_info[PackAPK.CFG_REGION].split(',') for r in new_list: if r.strip() != '': region_list.append(r) # 超过1个地区时需要带上全国地图 if len(region_list) > 1: region_list.append('0') keep_name_list = [] for region in region_list: keep_name = 'map_%s' % region keep_name_list.append(keep_name) map_path = utils.flat_path( os.path.join(temp_assets_path, 'res/hall/map')) if os.path.isdir(map_path): for f in os.listdir(map_path): full_path = os.path.join(map_path, f) if f not in keep_name_list and os.path.isdir(full_path): shutil.rmtree(full_path) # 如果不显示全国地图,则删除云动画的图片 if len(region_list) <= 1: cloud1_path = os.path.join(map_path, "yun1.png") cloud2_path = os.path.join(map_path, "yun2.png") os.remove(cloud1_path) os.remove(cloud2_path) # 清理 assets 文件夹,只保留需要的文件 assets_path = os.path.join(self.proj_android_path, 'assets') file_recorder = utils.FileRollback() for f in PackAPK.NEED_BACKUP_ASSETS: file_recorder.record_file( utils.flat_path(os.path.join(assets_path, f))) if os.path.isdir(assets_path): shutil.rmtree(assets_path) if not self.no_rollback: file_recorder.do_rollback() # 进行加密或者直接拷贝资源文件 if os.path.isdir(temp_assets_path): # 生成 native 环境信息文件 params = None if need_remove_agora: # 需要移除 agora params = {"no_agora": "true"} utils.write_native_info( os.path.join(temp_assets_path, 'src/NativeInfo.lua'), params) if not self.no_encrypt: self._do_encrypt(temp_assets_path, assets_path) else: copy_cfg = { 'from': '.', 'to': '.', 'exclude': ['**/.DS_Store'] } excopy.copy_files_with_config(copy_cfg, temp_assets_path, assets_path) f = open(os.path.join(assets_path, 'src/myflag'), 'w') f.write('') f.close() Logging.debug_msg('资源文件处理结束') # 处理 SDK 相关的配置 Logging.debug_msg('处理 SDK 相关的配置开始') sdks_cfg = apk_cfg_info.get(PackAPK.CFG_SDKS, {}) remove_weixin = apk_cfg_info.get(PackAPK.CFG_REMOVE_WEIXIN, False) remove_youqu = apk_cfg_info.get(PackAPK.CFG_REMOVE_YOUQU, False) sdk_mgr = SDKManager.SDKManager(rollback_obj, pkg_name, sdks_cfg, remove_weixin, remove_youqu, self.build_gradle, self.proj_android_path, self.proj_androidStudio_path) sdk_mgr.prepare_sdk() Logging.debug_msg('处理 SDK 相关的配置结束') # 检查是否需要删除 agora 的库文件 if need_remove_agora: self._do_remove_agora(rollback_obj) # 最终 apk 文件名格式为:GAMENAME_PKGNAME_APPID_CHANNELID_VERNAME_VERCODE.apk Logging.debug_msg('生成apk文件名') name_infos = [pkg_name, app_id, channel_id, ver_name, ver_code] remark = utils.non_unicode_str(apk_cfg_info.get( PackAPK.CFG_REMARK, "")) if len(remark) is not 0: name_infos.append(remark) if self.no_encrypt: name_infos.append('NO_ENCRYPT') # 是否删除 agora # if need_remove_agora: # name_infos.append("NO_AGORA") # 文件名增加时间戳 Logging.debug_msg('文件名增加时间戳') cur_time_str = datetime.datetime.fromtimestamp( time.time()).strftime('%Y%m%d_%H%M%S') name_infos.append(cur_time_str) apk_name = '_'.join(name_infos) + '.apk' out_file_path = os.path.join(self.output_path, apk_name) if not os.path.isdir(self.output_path): os.makedirs(self.output_path) if self.build_gradle: # 进行 gradle 打包 Logging.debug_msg('进行 gradle 打包') try: self.build_apk_gradle(rollback_obj, cfg_dir, apk_cfg_info, apk_name) self.build_result['%s_%s' % (app_id, channel_id)] = out_file_path except: print 'traceback.format_exc():\n%s' % traceback.format_exc() return else: # 进行 ant 打包 Logging.debug_msg('进行 ant 打包') keystore_path = utils.flat_path( os.path.join( cfg_dir, apk_cfg_info[PackAPK.CFG_SIGN][PackAPK.CFG_SIGN_FILE])) keystore_pass = apk_cfg_info[PackAPK.CFG_SIGN][ PackAPK.CFG_SIGN_PASSWORD] alias = apk_cfg_info[PackAPK.CFG_SIGN][PackAPK.CFG_SIGN_ALIAS] alias_pass = apk_cfg_info[PackAPK.CFG_SIGN][ PackAPK.CFG_SIGN_ALIAS_PASSWORD] ant_cmd = PackAPK.ANT_CMD_FMT % (self.ant_bin, self.build_xml, self.sdk_root, keystore_path, alias, keystore_pass, alias_pass) try: utils.run_shell(ant_cmd) except: Logging.warn_msg('打包失败') return # 将 apk 包拷贝到指定位置 build_path = utils.flat_path( os.path.join(self.proj_android_path, 'bin/%s-release.apk' % self.proj_name)) shutil.copy(utils.get_sys_encode_str(build_path), utils.get_sys_encode_str(out_file_path)) Logging.debug_msg('生成的 apk 文件路径:%s' % out_file_path) self.build_result['%s_%s' % (app_id, channel_id)] = out_file_path try: shutil.rmtree(temp_assets_path) except: pass
def output_info(self, msg): if not self.quiet: Logging.debug_msg(msg)
for file in files: index = file.find(".") prefix = file[index + 1:] if prefix in img_prefix: if len(dirs) > 0: image = os.path.join(cur_dir, root, dirs[0], file) print("image==========" + image) else: image = os.path.join(cur_dir, root, file) cmd = compressCmd + image os.popen(cmd, 'r') print(cmd) def getAllGames(cmd): if cmd == 'all': return mjGames + pkGames elif cmd == 'allmj': return mjGames elif cmd == 'allpk': return pkGames else: return [] if __name__ == "__main__": if len(sys.argv) < 1: Logging.debug_msg("输入短名错误!") else: start(sys.argv[1])