find-object-bundle-builder/FindObjectBundleBuilder/Tools/build_package/build_package.py

471 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env python
# coding:utf-8
# python3 Tools/build_package/build_package.py -m Debug -p Android -u "/Applications/Unity/Hub/Editor/2020.3.48f1c1/Unity.app/Contents/MacOS/Unity" -a /Volumes/SeanSSD/gitea_ap_art_assets -r /Volumes/SeanSSD/unity_ap_bundle_resource/Bundles -l "test" -d false
import datetime
import json
import os
import shutil
import sys
import time
import upload_firebase_storage
import psd_convert.parse_psd as parse_psd
import config_convert.gen_levels_db as config_gen
curr_dir = os.path.split(os.path.abspath(__file__))[0]
print("curr_dir = " + curr_dir)
sys.path.append(os.path.join(curr_dir, "../"))
sys.path.append(os.path.join(curr_dir, "../ipm"))
sys.path.append(os.path.join(curr_dir, "../config_convert"))
import config
import utils as utils
from config_convert.game_play_type import MainPlayType
from optparse import OptionParser
from ipm.wechat_alert import wechat_alert
alert = wechat_alert()
# input_old_hash = ''
# input_new_hash = ''
# if len(sys.argv) > 1:
# input_old_hash = sys.argv[1]
# if input_old_hash == 'none' or input_old_hash == '_':
# input_old_hash = ''
# if len(sys.argv) > 2:
# input_new_hash = sys.argv[2]
# if input_new_hash == 'none' or input_new_hash == '_':
# input_new_hash = ''
# 主机IP
IP = ""
# Unity执行文件路径
UNITY_PATH = "/Applications/Unity/Hub/Editor/2021.3.45f1/Unity.app/Contents/MacOS/Unity"
# 项目路径
PROJECT_PATH = os.path.abspath(os.path.join(curr_dir, "../.."))
# resource目录最终要提交到CDN地文件
RESOURCE_ROOT = os.path.abspath(os.path.join(curr_dir, "../../Bundles"))
# cwd = os.getcwd()
# print(cwd)
if PROJECT_PATH == "":
print("未指定 UNITY_PATH 和 PROJECT_PATH")
exit()
def check_dir(path):
if not os.path.exists(path):
os.makedirs(path)
def isGitCommitFileExist():
return os.path.exists(opts.git_commit_hash_file)
def writeGitCommitLog(git_log_hash):
# 写入git_commit.txt文件
with open(opts.git_commit_hash_file, 'w') as f:
f.write(git_log_hash)
def recordNewGitHash(git_log_hash):
# 文件末尾新增一行记录新的git hash
with open(opts.git_hash_change_record_file, 'a') as f:
f.write(git_log_hash + '\n')
def isRecordGitHashFileExist():
return os.path.exists(opts.git_hash_change_record_file)
def getCurrentGitHash():
current_commit_hash = ''
if isGitCommitFileExist():
with open(opts.git_commit_hash_file, 'r') as f:
current_commit_hash = f.read()
print(f"current hash:{current_commit_hash}")
return current_commit_hash
def getNewGitHash():
sh1 = f"cd {opts.assets}"
sh2 = f"git log -1 --pretty=format:\"%H\""
f = os.popen(f"{sh1} && {sh2}")
new_commit_hash = f.read()
print(f"new hash:{new_commit_hash}")
return new_commit_hash
# endregion
def getModifyPic(git_dir):
pass
if opts.old_hash != '' and opts.new_hash != '':
current_commit_hash = opts.old_hash
new_commit_hash = opts.new_hash
else:
current_commit_hash = getCurrentGitHash()
new_commit_hash = getNewGitHash()
if not isGitCommitFileExist():
writeGitCommitLog(new_commit_hash)
current_commit_hash = new_commit_hash
if not isRecordGitHashFileExist():
recordNewGitHash(new_commit_hash)
if current_commit_hash == new_commit_hash:
config.notification_helper.append_msg("当前commit hash与新commit hash相同不需要构建")
return None
sh1 = f"cd {git_dir}"
sh2 = f"git log {new_commit_hash}...{current_commit_hash} --name-status"
f = os.popen(f"{sh1} && {sh2}")
modify_content_lines = f.readlines()
return modify_content_lines
def getModifyIdList(modify_content_lines):
assetLevelIdList = []
# thumLevelIdList = []
if modify_content_lines is None:
return assetLevelIdList#, thumLevelIdList
for line in modify_content_lines:
levelId = None
if line.startswith('D'):
continue
if '.zip' not in line:
continue
if '\t' in line and '\n' in line:
levelId = line.split('\t')[-1].split('/')[-1].split('.')[0]
else:
levelId = line.split('/')[-1].split('.')[0]
if levelId is None:
continue
if '.zip' in line and levelId not in assetLevelIdList:
assetLevelIdList.append(levelId)
# if '.jpg' in line and levelId not in thumLevelIdList:
# thumLevelIdList.append(levelId)
return assetLevelIdList#, thumLevelIdList
def generateLevelIdListByGitLog():
pass
global _levelModifiedIdList_
modify_content_lines = getModifyPic(opts.assets)
_levelModifiedIdList_ = getModifyIdList(modify_content_lines)
if len(opts.special) > 0 and opts.special != '0':
manual_modify_level_list = opts.special.split(',')
for level_id in manual_modify_level_list:
if level_id not in _levelModifiedIdList_:
_levelModifiedIdList_.append(level_id)
print(f'资源修改列表:{str(_levelModifiedIdList_)}')
config.notification_helper.append_msg(f"需要打包的资源:总共{len(_levelModifiedIdList_)}个 所有id: {str(_levelModifiedIdList_)}")
def update_new_git_hash():
if opts.old_hash != '' and opts.new_hash != '':
current_commit_hash = opts.old_hash
new_commit_hash = opts.new_hash
else:
current_commit_hash = getCurrentGitHash()
new_commit_hash = getNewGitHash()
if new_commit_hash != '' and new_commit_hash != current_commit_hash:
writeGitCommitLog(new_commit_hash)
recordNewGitHash(new_commit_hash)
config.notification_helper.append_msg(
f"[step] update git hash success, old hash:{current_commit_hash}, new hash:{new_commit_hash}")
else:
config.notification_helper.append_msg("[Error] current_commit_hash is empty")
def build_package(opts, modify_files):
"""
打资源包
:param opts: 打包参数
:param modify_files: 修改文件
:return: 无
"""
time_build_package_start = time.time()
params = "AppVersion=" + opts.appversion
now = datetime.datetime.now()
build_number = "{:2d}{:02d}{:02d}{:02d}{:02d}".format(int(now.year - 2000), int(now.month), int(now.day),
int(now.hour), int(now.minute))
params = params + " BuildVersion=" + build_number
params = params + " ResVersion=" + opts.resversion
params = params + " Platform=" + opts.platform
params = params + " BuildType=" + opts.buildtype
params = params + " Mode=" + opts.mode
params = params + " ModifyFiles=" + json.dumps(modify_files).replace(" ", "")
params = params + " ResourceRoot=" + RESOURCE_ROOT
print(params)
cmd = UNITY_PATH + " -quit " + " -batchmode " + " -projectPath " + PROJECT_PATH + " -executeMethod BuildBundlesHelper.BuildBundles " + params + " -logFile " + PROJECT_PATH + "/Logs/build_log.txt"
print(cmd)
# 调用Unity打包
os.system(cmd)
error = ''
is_build_success = False
# 热更Bundles文件输出文件夹Unity输出的源文件
output_dir = f"{RESOURCE_ROOT}/{opts.platform}"
bundle_file_path = output_dir + "/{}/" + build_number
if (opts.platform == "Android" or opts.platform == "iOS") and os.path.exists(output_dir):
for asset_id in modify_files:
asset_level_dir = bundle_file_path.format(asset_id)
asset_level_thum_dir = bundle_file_path.format(f"{asset_id}_thum")
isAppendNewLine = False
if not os.path.exists(asset_level_dir):
error = error + f"{asset_id}关卡资源打包失败 "
isAppendNewLine = True
if not os.path.exists(asset_level_thum_dir):
error = error + f"{asset_id}缩略图资源打包失败 "
isAppendNewLine = True
if isAppendNewLine:
error = error + "\n"
if error == '':
is_build_success = True
time_build_package_end = time.time()
if is_build_success:
config.notification_helper.append_msg(f"{opts.platform}平台 YooAsset资源构建成功, cost time:{(time_build_package_end - time_build_package_start):.0f}s")
else:
config.notification_helper.append_msg(f"{opts.platform}平台 YooAsset资源构建失败, error:{error}, 请前往Jenkins查看具体日志")
config.notification_helper.append_at_people(config.at_zhouzhuo)
return is_build_success, bundle_file_path
def check_params(opts):
print(opts)
msg = ''
build_types = ['BuildIn_All', 'Builtin_Hotupdate', 'BuildBundle', 'BuildPackage']
if opts.buildtype not in build_types:
msg = msg + '打包类型设置错误\n'
return msg
if __name__ == '__main__':
# region 创建打包的命令参数
parser = OptionParser()
# parser.add_option("-v", "--appversion", dest="appversion", help=('app版本, 显示在app详情内的'))
# parser.add_option("-r", "--resversion", dest="resversion", help=('资源版本版本'))
# parser.add_option("-b", "--buildtype", dest="buildtype", help=('打包类型, 选项: buildin/buildplayer/buildbundle'))
# parser.add_option("-g", "--aab", dest="aab", help=('是否为上传Google Play的aab包'))
parser.add_option("-m", "--mode", dest="mode", help=('打包模式, 选项: Release/Debug'))
parser.add_option("-p", "--platform", dest="platform", help=('打包平台 选项: Android/iOS'))
# parser.add_option("-u", "--unityexe", dest="unityexe", help=('Unity可执行文件'))
# parser.add_option("-a", "--assets", dest="assets", help=('美术资源路径'))
# parser.add_option("-r", "--resources", dest="resources", help=('导出资源路径'))
# parser.add_option("-l", "--log", dest="log", help=('美术资源最新的git log'))
# parser.add_option("-d", "--upload", dest="upload", help=('是否上传'))
parser.add_option("-s", "--special", dest="special", help=('指定构建的资源'))
parser.add_option("-n", "--new-hash", dest="new_hash", help=('最新的hash'))
parser.add_option("-o", "--old-hash", dest="old_hash", help=('老的hash'))
(opts, args) = parser.parse_args()
opts.appversion = "1.0.0"
opts.resversion = "1"
opts.buildtype = "BuildBundle"
opts.unityexe = "/Applications/Unity/Hub/Editor/2021.3.45f1/Unity.app/Contents/MacOS/Unity"
opts.upload = "true"
opts.log = "log/build.log"
opts.assets = "/Volumes/Predator/find-object/find-object-art"
opts.resources = "/Volumes/Predator/find-object/find-object-bundle-resource"
opts.workspace_path = config.machine_env_config.CD_MAC_STUDIO.value['workspace_path']
opts.git_commit_hash_file = os.path.join(opts.workspace_path, f'find_object_git_commit_hash_{opts.platform}.txt')
opts.git_hash_change_record_file = os.path.join(opts.workspace_path, f'find_object_git_hash_change_record_{opts.platform}.txt')
if opts.new_hash is None or opts.new_hash == 'none' or opts.new_hash == '_':
opts.new_hash = ''
if opts.old_hash is None or opts.old_hash == 'none' or opts.old_hash == '_':
opts.old_hash = ''
# 本地自测适用
# opts.aab = "false"
# opts.mode = "Debug"
# opts.platform = "Android"
# opts.assets = "/Users/a0729/gogs.git/find-object-art"
# opts.resources = "/Users/a0729/gogs.git/find-object-bundle-resource"
# opts.unityexe = "/Applications/Unity/Hub/Editor/2021.3.45f1/Unity.app/Contents/MacOS/Unity"
# opts.log = "log/build.log"
# opts.upload = "false"
# opts.special = "main_30point_ws20250422_1"
# opts.special = "0"
#
print(f'test-- {opts}')
is_upload = True if opts.upload == "true" else False
check_msg = check_params(opts)
if check_msg != "":
check_msg = check_msg + '请使用 python Tools/BuildPackage/build_package.py -h 查看使用规则'
print(check_msg)
time_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
title = '客户端构建失败'
content = time_str + '\n\n > ' + check_msg
print(content)
exit()
if opts.unityexe != "":
UNITY_PATH = opts.unityexe
RESOURCE_ROOT = os.path.abspath(os.path.join(curr_dir, "../../Bundles"))
# endregion
mainPlayType = MainPlayType()
# region 清空各玩法软链接资源列表
try:
for gameplay in mainPlayType.all_main_play_type_list:
for root, dirs, files in os.walk(os.path.join(PROJECT_PATH, "Assets/AssetRaw/UIRaw/Raw/Level/{}".format(gameplay))):
for dir in dirs:
os.unlink(os.path.join(root, dir))
except Exception as e:
print(e)
for gameplay in mainPlayType.all_main_play_type_list:
for root, dirs, files in os.walk(os.path.join(PROJECT_PATH, "Assets/AssetRaw/UIRaw/Raw/Level/{}".format(gameplay))):
for file in files:
os.remove(os.path.join(root, file))
try:
for gameplay in mainPlayType.all_main_play_type_list:
for root, dirs, files in os.walk(os.path.join(PROJECT_PATH, "Assets/AssetRaw/UIRaw/Raw/Thum/{}".format(gameplay))):
for file in files:
os.unlink(os.path.join(root, file))
except Exception as e:
print(e)
for gameplay in mainPlayType.all_main_play_type_list:
for root, dirs, files in os.walk(os.path.join(PROJECT_PATH, "Assets/AssetRaw/UIRaw/Raw/Thum/{}".format(gameplay))):
for file in files:
os.remove(os.path.join(root, file))
# endregion
# region 检索需要需要打包的关卡
modify_files = []
# if opts.special != "0":
# modify_files = opts.special.split(",")
# else:
#暂时不用这个方案
# 获取最近6小时内的提交记录用于解析psd
# platform = "android" if opts.platform == "Android" else "ios"
# with open(os.path.join(opts.assets, f"git_{platform}_commit_change_files.txt"), "r") as f:
# lines = f.readlines()
# for aline in lines:
# aline = aline.replace("\"", "").replace("\n", "").replace("\r", "")
# if aline.endswith(".zip"):
# base_dir = aline.split("/")[0]
# file_name = os.path.basename(aline)
# asset_id = file_name.split(".")[0]
# if asset_id not in modify_files:
# modify_files.append(asset_id)
#获取git 提交来得到修改的关卡
generateLevelIdListByGitLog()
if len(_levelModifiedIdList_) <= 0:
config.notification_helper.append_msg('没有需要构建的资源')
else:
modify_files = _levelModifiedIdList_
print(f"modify_files = {modify_files}")
# alert.alert(f"构建资源列表:{str(modify_files)}")
tmstp1 = time.time()
# 解析psd => Unity工程
for gameplay in mainPlayType.all_main_play_type_list:
gameplay_modify_file_list = [s for s in modify_files if gameplay in s]
if len(gameplay_modify_file_list) > 0:
gameplay_all_parsed_assets = parse_psd.parse_psd(f"{opts.assets}/Level/{gameplay}", opts.resources,
gameplay_modify_file_list, opts.platform)
tmstp2 = time.time()
print(f"{len(modify_files)}个资源, 解析psd耗时{tmstp2 - tmstp1}")
config.notification_helper.append_msg(f"解析{len(modify_files)}个psd资源耗时{(tmstp2 - tmstp1):.0f}s")
# endregion
# region 建立需要打包的资源软连接
metas = []
for asset_id in modify_files:
asset_gameplay = None
for gameplay in mainPlayType.all_main_play_type_list:
if gameplay in asset_id:
asset_gameplay = gameplay
break
if asset_gameplay is not None:
src = os.path.join(opts.resources, f"Raw/Level/{asset_gameplay}/{asset_id}")
dest = os.path.join(PROJECT_PATH, f"Assets/AssetRaw/UIRaw/Raw/Level/{asset_gameplay}/{asset_id}")
utils.mkdirs(os.path.dirname(dest))
if os.path.exists(src):
os.symlink(src, dest)
src = os.path.join(opts.resources, f"Raw/Level/{asset_gameplay}/{asset_id}.meta")
dest = os.path.join(PROJECT_PATH, f"Assets/AssetRaw/UIRaw/Raw/Level/{asset_gameplay}/{asset_id}.meta")
if os.path.exists(src):
os.symlink(src, dest)
else:
metas.append(["Level/{}".format(asset_gameplay), dest])
src = os.path.join(opts.resources, f"Raw/Thum/{asset_gameplay}/{asset_id}_thum.png")
dest = os.path.join(PROJECT_PATH, f"Assets/AssetRaw/UIRaw/Raw/Thum/{asset_gameplay}/{asset_id}_thum.png")
utils.mkdirs(os.path.dirname(dest))
if os.path.exists(src):
os.symlink(src, dest)
src = os.path.join(opts.resources, f"Raw/Thum/{asset_gameplay}/{asset_id}_thum.png.meta")
dest = os.path.join(PROJECT_PATH, f"Assets/AssetRaw/UIRaw/Raw/Thum/{asset_gameplay}/{asset_id}_thum.png.meta")
if os.path.exists(src):
os.symlink(src, dest)
else:
metas.append(["Thum/{}".format(asset_gameplay), dest])
print(f"{asset_gameplay}玩法,{asset_id}资源建立软链接")
# endregion
# region 获取资源不同点个数
asset_find_num_dict = {}
for asset_id in modify_files:
asset_gameplay = None
for gameplay in mainPlayType.all_main_play_type_list:
if gameplay in asset_id:
asset_gameplay = gameplay
break
if asset_gameplay is not None:
src = os.path.join(opts.resources, f"Raw/Level/{asset_gameplay}/{asset_id}/titem")
find_num = 0
if os.path.exists(src):
for file in os.listdir(src):
if ".meta" in file:
continue
if f"{asset_id}_titem" in file:
find_num = find_num + 1
else:
print(f"关卡资源ID asset_gameplay:{asset_gameplay},asset_id={asset_id} 不存在titem目录")
asset_find_num_dict[asset_id] = find_num
print(f"关卡资源ID asset_id = {asset_id}, 寻找物品个数find_num = {find_num}")
# endregion
# region 开始打包 以及上传资源
tmBuildPackage1 = time.time()
is_build_success, ab_path = build_package(opts, modify_files)
tmBuildPackage2 = time.time()
print(f"{len(modify_files)}个资源, unity打包耗时{tmBuildPackage2 - tmBuildPackage1}")
if is_build_success and is_upload:
print(f"开始上传ab包到firebase")
storage_path = "Bundles/{}".format(opts.platform)
# 上传ab包到firebase
tmUploadPackage1 = time.time()
upload_firebase_storage.upload_package_assets(ab_path, modify_files, storage_path, asset_find_num_dict,
{config.meta_encryption: "stream", config.meta_spriteatlas: "1"})
tmUploadPackage2 = time.time()
print(f"{len(modify_files)}个资源, firebase上传耗时{tmUploadPackage2 - tmUploadPackage1}")
config.notification_helper.append_msg(f"上传{len(modify_files)}个资源到firebase耗时{(tmUploadPackage2 - tmUploadPackage1):.0f}s")
update_new_git_hash()
# 遍历ab包生成所有关卡表
tmUpdateSheet1 = time.time()
config_gen.update_all_levels_google_sheet(opts.platform)
tmUpdateSheet2 = time.time()
config.notification_helper.append_msg(f"{opts.platform}平台所有资源表更新完成!")
print(f"{len(modify_files)}个资源, 更新文档耗时:{tmUpdateSheet2 - tmUpdateSheet1}")
# endregion
# region 打包完成后将meta文件保存到opts.resources
for meta in metas:
print(f"meta = {meta}")
print(f"copy {meta[1]} to {os.path.join(opts.resources, 'Raw', meta[0])}")
shutil.copy(meta[1], os.path.join(opts.resources, 'Raw', meta[0]))
# endregion
config.notification_helper.append_end_msg()
alert.alert(config.notification_helper.get_msg(), config.notification_helper.get_people_list())