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_add_check_files(self, key, value): unix_key = key.replace('\\', '/') if unix_key not in self.check_files: self.check_files[unix_key] = value else: Logging.warn_msg("%s 冲突:%s 和 %s" % (unix_key, self.check_files[unix_key], value))
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 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 run_shell(cmd, cwd=None, quiet=False): if not quiet: Logging.log_msg('Running command: %s\n' % cmd) p = subprocess.Popen(cmd, shell=True, cwd=cwd) p.wait() if p.returncode: raise_known_error('Command %s failed' % cmd, p.returncode) return p.returncode
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 __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 getCompileJsListByModule(self, module): jsList = [] preList = [] postList = [] gameConfigPath = os.path.join(self.proj_path, "games", module, "wxpack.json") if os.path.exists(gameConfigPath): gameConfig = utils.parse_json(gameConfigPath) preposition = gameConfig.get("preposition", []) for preFile in preposition: jsList.append(preFile) preList.append(preFile) postposition = gameConfig.get("postposition", []) for postFile in postposition: postList.append(postFile) else: Logging.log_msg("wxpack.json 文件不存在 %s" % gameConfigPath) for parent, dirnames, filenames in os.walk(self.games_path): relpath = os.path.relpath(parent, os.path.join(parent, "..")) isSamePath = os.path.normpath( os.path.abspath(os.path.join( parent, ".."))) == os.path.normpath( os.path.abspath(self.games_path)) if module == relpath and isSamePath == True: for p, dirname, filenames in os.walk(parent): for filename in filenames: token = filename.split(".") if len(token) != 2 or filename.split(".")[1] != "js": continue filename = os.path.join(os.path.normpath(p), filename) filename = os.path.relpath(filename, self.proj_path) filename = filename.replace("\\", "/") isPass = False for postFile in postList: if postFile == filename: isPass = True for preFile in preList: if preFile == filename: isPass = True if 'manifest' in filename: isPass = True if isPass == False: jsList.append(filename) for postFile in postList: jsList.append(postFile) return jsList
def run_shell(cmd, cwd=None, quiet=False): if not quiet: cwd_info = '' if cwd: cwd_info = ' (cwd path : %s) ' % cwd Logging.log_msg('Running command%s: %s\n' % (cwd_info, cmd)) p = subprocess.Popen(cmd, shell=True, cwd=cwd) p.wait() if p.returncode: raise_known_error('Command %s failed' % cmd, p.returncode) return p.returncode
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 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 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 __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 __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 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 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 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 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_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)