From eb9da1489d03f31589b63d752cb05a908775512a Mon Sep 17 00:00:00 2001 From: luojian Date: Tue, 28 Oct 2025 10:04:16 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/build.py | 10 + scripts/context.py | 56 ++++++ scripts/project_build.py | 14 +- scripts/project_copy.py | 62 +++++- scripts/project_end.py | 2 +- scripts/project_init.py | 77 +++++++- scripts/project_interface.py | 4 + scripts/project_proguard.py | 5 + scripts/project_res_md5.py | 5 + scripts/project_res_string.py | 3 + scripts/project_update.py | 306 +---------------------------- scripts/project_update_config.py | 71 +++++++ scripts/project_update_game_res.py | 170 ++++++++++++++++ scripts/project_update_icon.py | 39 ++++ scripts/project_update_image.py | 52 +++++ scripts/project_update_keystore.py | 41 ++++ scripts/task.py | 1 - 17 files changed, 596 insertions(+), 322 deletions(-) create mode 100644 scripts/project_update_config.py create mode 100644 scripts/project_update_game_res.py create mode 100644 scripts/project_update_icon.py create mode 100644 scripts/project_update_image.py create mode 100644 scripts/project_update_keystore.py diff --git a/scripts/build.py b/scripts/build.py index 12b6a0f..6e0b955 100644 --- a/scripts/build.py +++ b/scripts/build.py @@ -9,6 +9,11 @@ from .project_proguard import ProjectProguard from .project_res_md5 import ProjectResMd5 from .project_res_string import ProjectResString from .project_update import ProjectUpdate +from .project_update_config import ProjectUpdateConfig +from .project_update_game_res import ProjectUpdateGameRes +from .project_update_icon import ProjectUpdateIcon +from .project_update_image import ProjectUpdateImage +from .project_update_keystore import ProjectUpdateKeystore from .project_upload import ProjectUpload @@ -18,6 +23,11 @@ def run(context: Context): ProjectInit(context), ProjectCopy(context), ProjectResMd5(context), + ProjectUpdateKeystore(context), + ProjectUpdateConfig(context), + ProjectUpdateIcon(context), + ProjectUpdateImage(context), + ProjectUpdateGameRes(context), ProjectUpdate(context), ProjectResString(context), ProjectInterface(context), diff --git a/scripts/context.py b/scripts/context.py index 21c346f..afb3bae 100644 --- a/scripts/context.py +++ b/scripts/context.py @@ -1,9 +1,12 @@ +import os from dataclasses import dataclass import json @dataclass class Context: + project_original_path: str + local_repo_commit: str repo_url: str = "" repo_branch: str = "" repo_commit: str = "" @@ -18,6 +21,7 @@ class Context: project_original_path: str = "project/original" temp_project_path: str = "" + temp_project_config_path: str = "" # 本地的版本号 local_repo_branch: str = "" local_repo_commit: str = "" @@ -44,6 +48,24 @@ class Context: string: dict = None + update_code: bool = True + update_config: bool = True + update_keystore: bool = True + update_res_img: bool = True + update_res_icon: bool = True + update_res_unity: bool = True + config_path: str = "" + keystore_path: str = "" + res_img_path: str = "" + res_icon_path: str = "" + res_unity_path: str = "" + + config_config_md5: str = "" + config_keystore_md5: str = "" + config_res_img_md5: str = "" + config_res_icon_md5: str = "" + config_res_unity_md5: str = "" + @classmethod def from_json(cls, json_str: str): data = json.loads(json_str) @@ -58,3 +80,37 @@ class Context: if self.app_name: return self.app_name return self.get_config("app_name") + + def get_cache_config(self) -> dict[str, str]: + try: + path = os.path.join(self.temp_project_config_path, "config.json") + args = json.load(open(path, "r", encoding="utf-8")) + return args + except: + return {} + + def get_cache_config_from_key(self, key: str, default: str = "") -> str: + try: + return self.get_cache_config().get(key, default) + except: + return default + + def save_cache_config(self, key: str, value: str): + path = os.path.join(self.temp_project_config_path, "config.json") + config = self.get_cache_config() + config[key] = value + json.dump(config, open(path, "w", encoding="utf-8"), indent=4) + + def get_map(self) -> dict[str, str]: + try: + path = os.path.join(self.temp_project_config_path, "map.json") + return json.load(open(path, "r", encoding="utf-8")) + except: + return {} + + def save_map(self, map: dict[str, str]): + path = os.path.join(self.temp_project_config_path, "map.json") + json.dump(map, open(path, "w", encoding="utf-8"), indent=4) + + def get_map_from_key(self, file_name) -> str: + return self.get_map().get(file_name, file_name) diff --git a/scripts/project_build.py b/scripts/project_build.py index 1349273..29c399e 100644 --- a/scripts/project_build.py +++ b/scripts/project_build.py @@ -39,7 +39,7 @@ class ProjectBuild(Task): def execute(self): self.init() # self.save_project() - self.build_apk() + # self.build_apk() self.build_aab() self.copy_to_out() pass @@ -54,12 +54,12 @@ class ProjectBuild(Task): def copy_to_out(self): app_logger().debug(f"copy_to_out start.") - target = f"{self.context.temp_project_path}{os.sep}build{os.sep}outputs{os.sep}apk{os.sep}lawnWithQuickstepPlay{os.sep}debug" - FileUtils.copy(find_path(target, "apk"), self.context.out_debug_apk) - app_logger().debug(f"copy_to_out debug apk = {self.context.out_debug_apk}") - target = f"{self.context.temp_project_path}{os.sep}build{os.sep}outputs{os.sep}apk{os.sep}lawnWithQuickstepPlay{os.sep}release" - FileUtils.copy(find_path(target, "apk"), self.context.out_release_apk) - app_logger().debug(f"copy_to_out release apk = {self.context.out_release_apk}") + # target = f"{self.context.temp_project_path}{os.sep}build{os.sep}outputs{os.sep}apk{os.sep}lawnWithQuickstepPlay{os.sep}debug" + # FileUtils.copy(find_path(target, "apk"), self.context.out_debug_apk) + # app_logger().debug(f"copy_to_out debug apk = {self.context.out_debug_apk}") + # target = f"{self.context.temp_project_path}{os.sep}build{os.sep}outputs{os.sep}apk{os.sep}lawnWithQuickstepPlay{os.sep}release" + # FileUtils.copy(find_path(target, "apk"), self.context.out_release_apk) + # app_logger().debug(f"copy_to_out release apk = {self.context.out_release_apk}") target = f"{self.context.temp_project_path}{os.sep}build{os.sep}outputs{os.sep}bundle{os.sep}lawnWithQuickstepPlayRelease" FileUtils.copy(find_path(target, "aab"), self.context.out_release_aab) app_logger().debug(f"copy_to_out end.") diff --git a/scripts/project_copy.py b/scripts/project_copy.py index 2757558..67a2430 100644 --- a/scripts/project_copy.py +++ b/scripts/project_copy.py @@ -1,21 +1,63 @@ +import os.path +import shutil + +from git import Repo + from scripts.task import Task from utils import FileUtils -from utils import TimeUtils from utils.logger_utils import app_logger +def clean_workspace(repo): + # 1. 放弃工作区所有已跟踪文件的修改(包括恢复被删除的已跟踪文件) + # - checkout 命令会将工作区文件重置为当前 HEAD 版本的状态 + repo.git.checkout('.') + + # 2. 删除工作区所有未跟踪的文件和目录(包括空目录) + # - -f:强制删除(避免询问) + # - -d:同时删除未跟踪的目录 + repo.git.clean('-fd') + + print("工作空间已清空,恢复到当前分支最新提交状态") + + class ProjectCopy(Task): def execute(self): - self.init() + if os.path.exists(self.context.temp_project_path): + temp_path = os.path.join(self.context.temp_project_path, "build", "outputs") + if os.path.exists(temp_path): + shutil.rmtree(temp_path) + + temp_path = os.path.join(self.context.temp_project_path, "build", "generated") + if os.path.exists(temp_path): + shutil.rmtree(temp_path) + + app_logger().debug("project '{}' to '{}'".format(self.context.temp_project_path, "项目已经存在了")) + repo = Repo(self.context.temp_project_path) + temp_repo_commit = repo.head.commit.hexsha[:10] + app_logger().debug( + f"project '{self.context.temp_project_path}' , local '{temp_repo_commit}' remote '{self.context.local_repo_commit}") + if temp_repo_commit != self.context.local_repo_commit: + app_logger().debug("本地代码有变动,需要更新临时的目录") + # 本地分支和远程的分支不一样 + clean_workspace(repo) + origin = repo.remote("origin") + origin.fetch() + repo.git.checkout(self.context.local_repo_commit) + self.context.update_code = True + + self.context.update_config = True + self.context.update_keystore = True + self.context.update_res_img = True + self.context.update_res_icon = True + self.context.update_res_unity = True + else: + self.context.update_code = False + app_logger().debug("本地的代码没有变动,无需更新") + return + + self.context.update_code = True result = FileUtils.copy(self.context.project_original_path, self.context.temp_project_path) app_logger().debug("Copied project '{}' to '{}'".format(self.context.project_original_path, result)) pass - - def init(self): - self.context.temp_project_path = self.context.project_original_path.replace( - "original", self.context.package_name.replace(".", "_") + TimeUtils.get_formatted_time(format_str="%H%M%S") - ) - # self.context.temp_project_path = self.context.project_original_path - - pass diff --git a/scripts/project_end.py b/scripts/project_end.py index 106eb2e..f090d70 100644 --- a/scripts/project_end.py +++ b/scripts/project_end.py @@ -4,5 +4,5 @@ from utils import FileUtils class ProjectEnd(Task): def execute(self): - FileUtils.delete(self.context.temp_project_path, True) + # FileUtils.delete(self.context.temp_project_path, True) pass diff --git a/scripts/project_init.py b/scripts/project_init.py index 27bafb3..bab5d1d 100644 --- a/scripts/project_init.py +++ b/scripts/project_init.py @@ -1,6 +1,11 @@ +import os import time from git import Repo, RemoteProgress + +from utils import FileUtils +from utils.logger_utils import app_logger +from .context import Context from .task import Task @@ -10,7 +15,72 @@ def progress(op_code, cur_count, max_count=None, message=''): print(f"操作: {op_code}, 进度: {cur_count}/{max_count}, 消息: {message}") +def check_config_exists(*file_paths): + # game_config/com.diy.emoticon.free.zcaqf.game.launcher/com.diy.emoticon.free.zcaqf.game.launcher_android.zip + # game_config/com.emoticon.diy.znfav.launcher.free/com.emoticon.diy.znfav.launcher.free_android.zip + # 遍历检查每个文件 + if file_paths is None: + file_paths = [] + for file_path in file_paths: + if not os.path.exists(file_path): + # 抛出异常并说明缺失的文件路径 + raise FileNotFoundError(f"配置文件不存在: {file_path}") + + class ProjectInit(Task): + + def __init__(self, context: Context): + super().__init__(context) + self.context.temp_project_path = self.context.project_original_path.replace("original", + "V1_" + self.context.package_name.replace( + ".", "_")) + self.context.temp_project_config_path = self.context.temp_project_path + "_config" + if not os.path.exists(self.context.temp_project_config_path): + os.makedirs(self.context.temp_project_config_path) + + config_md5 = context.get_cache_config_from_key("config", "") + keystore_md5 = context.get_cache_config_from_key("keystore", "") + res_img_md5 = context.get_cache_config_from_key("res_img", "") + res_icon_md5 = context.get_cache_config_from_key("res_icon", "") + res_unity_md5 = context.get_cache_config_from_key("res_unity", "") + + app_logger().debug(f"temp project res md5 , " + f"config : {config_md5} , " + f"keystore : {keystore_md5} , " + f"res_img : {res_img_md5} , " + f"res_icon : {res_icon_md5} , " + f"res_unity : {res_unity_md5}") + + config_root_path = os.path.join("game_config", self.context.package_name) + + self.context.config_path = os.path.join(config_root_path, f"{self.context.package_name}_android.zip") + self.context.keystore_path = "" + for i in os.listdir(config_root_path): + if i.endswith(".keystore"): + self.context.keystore_path = os.path.join(config_root_path, i) + + self.context.res_img_path = os.path.join(config_root_path, "drawable-xxhdpi.zip") + self.context.res_icon_path = os.path.join(config_root_path, "icon.zip") + self.context.res_unity_path = os.path.join(config_root_path, "unityLibrary.zip") + + check_config_exists(self.context.config_path, + self.context.keystore_path, + self.context.res_img_path, + self.context.res_icon_path, + self.context.res_unity_path) + + self.context.config_config_md5 = FileUtils.get_md5(self.context.config_path) + self.context.config_keystore_md5 = FileUtils.get_md5(self.context.keystore_path) + self.context.config_res_img_md5 = FileUtils.get_md5(self.context.res_img_path) + self.context.config_res_icon_md5 = FileUtils.get_md5(self.context.res_icon_path) + self.context.config_res_unity_md5 = FileUtils.get_md5(self.context.res_unity_path) + + self.context.update_config = config_md5 != self.context.config_config_md5 + self.context.update_keystore = keystore_md5 != self.context.config_keystore_md5 + self.context.update_res_img = res_img_md5 != self.context.config_res_img_md5 + self.context.update_res_icon = res_icon_md5 != self.context.config_res_icon_md5 + self.context.update_res_unity = res_unity_md5 != self.context.config_res_unity_md5 + def execute(self): try: repo = Repo(self.context.project_original_path) @@ -45,12 +115,13 @@ class ProjectInit(Task): local_branch.set_tracking_branch(repo.remotes[remote_name].refs[branch_name]) # 设置跟踪 local_branch.checkout() # 切换到该分支 + # 拉取最新代码 + repo.remotes.origin.pull() + self.context.local_repo_branch = repo.active_branch.name self.context.local_repo_commit = repo.head.commit.hexsha[:10] - # 拉取最新代码 - repo.remotes.origin.pull() - print("当前分支:" + repo.active_branch.name) + print("当前分支:" + repo.active_branch.name, self.context.local_repo_commit) pass else: raise Exception(f"No commit to {self.context.repo_commit}") diff --git a/scripts/project_interface.py b/scripts/project_interface.py index 1650ba9..fe8a257 100644 --- a/scripts/project_interface.py +++ b/scripts/project_interface.py @@ -4,6 +4,7 @@ import os import hashlib from utils import FileUtils +from utils.logger_utils import app_logger def string_to_md5(text): @@ -65,6 +66,9 @@ class ProjectInterface(Task): f_out.write(line) def execute(self): + if not self.context.update_code: + app_logger().info("代码没有更新,不需要处理 unity 接口") + return self.unity_proxy_api_file = os.path.join(self.context.temp_project_path, "launcher-game/src/com/game/hachisdk/unity/UnityProxyApi.java".replace( diff --git a/scripts/project_proguard.py b/scripts/project_proguard.py index 2c824af..d69c23a 100644 --- a/scripts/project_proguard.py +++ b/scripts/project_proguard.py @@ -276,6 +276,10 @@ class ProjectProguard(Task): pass def execute(self): + if not self.context.update_code: + app_logger().info("No update project proguard found") + self.context.proguard_dict = self.context.get_map() + return self.root = self.context.temp_project_path self.module_path = os.path.join(self.root, "launcher-game") self.code_path = os.path.join(self.module_path, "src") @@ -324,6 +328,7 @@ class ProjectProguard(Task): encrypt_xml_resources(self.string_path, False, string_to_md5(self.context.package_name).upper()) + self.context.save_map(self.context.proguard_dict) app_logger().info(json.dumps(self.context.proguard_dict, indent=4)) # if __name__ == '__main__': diff --git a/scripts/project_res_md5.py b/scripts/project_res_md5.py index 492a431..dc8b338 100644 --- a/scripts/project_res_md5.py +++ b/scripts/project_res_md5.py @@ -4,6 +4,8 @@ import hashlib from PIL import Image import random +from utils.logger_utils import app_logger + def get_md5(file_path): """计算文件的MD5值""" @@ -64,6 +66,9 @@ def modify_one_pixel(input_path, output_path): class ProjectResMd5(Task): def execute(self): + if not self.context.update_code: + app_logger().info("不需要更新res md5") + return path = f"launcher-game/res/" root_path = os.path.join(self.context.temp_project_path, path) for i in os.listdir(root_path): diff --git a/scripts/project_res_string.py b/scripts/project_res_string.py index 19a4378..5ec4f1f 100644 --- a/scripts/project_res_string.py +++ b/scripts/project_res_string.py @@ -37,6 +37,9 @@ class ProjectResString(Task): app_logger().debug("路径不存,不操作了,后续可以给他创建出来:" + string_path + "\t" + json.dumps(res, indent=4)) def execute(self): + if not self.context.update_code: + app_logger().info("代码没有更新,不需要处理资源") + return for key in self.context.string.keys(): launcher = key.replace('base', '') if launcher: diff --git a/scripts/project_update.py b/scripts/project_update.py index 77a2038..eefe776 100644 --- a/scripts/project_update.py +++ b/scripts/project_update.py @@ -1,86 +1,11 @@ import os.path -from pathlib import Path import re -from xml.dom import minidom - -import javaproperties -import requests -from lxml import etree -import xml.etree.ElementTree as ET from scripts.context import Context from scripts.task import Task -from utils import FileUtils from utils.logger_utils import app_logger -def process_manifest(input_path, output_path): - # 解析XML文件 - tree = ET.parse(input_path) - root = tree.getroot() - - # 定义命名空间映射 - android_namespace = 'http://schemas.android.com/apk/res/android' - ET.register_namespace('android', android_namespace) - namespaces = {'android': android_namespace} - - # 处理application标签,移除所有属性 - application = root.find('application') - if application is not None: - # 保存所有子节点 - children = list(application) - # 清除application标签 - root.remove(application) - # 创建新的application标签(无属性) - new_application = ET.Element('application') - # 添加回所有子节点 - for child in children: - new_application.append(child) - # 将新的application标签添加回root - root.append(new_application) - - # 查找并删除指定的activity节点 - activity_to_remove = new_application.find( - ".//activity[@android:name='com.unity3d.player.UnityPlayerActivity']", - namespaces=namespaces - ) - if activity_to_remove is not None: - new_application.remove(activity_to_remove) - - # 保存处理后的XML,保留android命名空间前缀 - rough_string = ET.tostring(root, 'utf-8') - reparsed = minidom.parseString(rough_string) - pretty_xml = reparsed.toprettyxml(indent=" ", encoding='utf-8') - - # 去除空行 - lines = pretty_xml.decode('utf-8').split('\n') - non_empty_lines = [line for line in lines if line.strip() != ''] - pretty_xml = '\n'.join(non_empty_lines).encode('utf-8') - - with open(output_path, 'wb') as f: - f.write(pretty_xml) - - print(f"处理完成,结果已保存到 {output_path}") - - -def uncomment_line(content, line_pattern): - """ - 取消指定行的注释 - - :param content: 文件内容 - :param line_pattern: 要取消注释的行内容(不含前导//和空格) - :return: 更新后的内容 - """ - # 匹配以//开头,后跟任意空格,然后是目标行内容 - pattern = rf'^(\s*)//\s*({re.escape(line_pattern)}\s*)$' - - # 替换为去注释版本(保留原有缩进) - replacement = rf'\1\2' - - updated_content = re.sub(pattern, replacement, content, flags=re.MULTILINE) - return updated_content - - def update_gradle_variable(content, variable_name, new_value): """ 更新 Gradle 文件中的 final def 变量值 @@ -166,12 +91,8 @@ def update_gradle_property(content, key, new_value): return updated_content -LAUNCHER_CODE_PATH = f"LauncherCode/src/com/launchercode".replace("/", os.sep) -GAME_ACTIVITY_PATH = f"launcher-game/src/com/game/launcher/activity/GLGameActivity.kt".replace("/", os.sep) GAME_ACTIVITY_PATH_2 = f"LauncherCode/src/com/launchercode/activity/GameActivity.kt".replace("/", os.sep) -ANDROID_MANIFEST_PATH = f"launcher-game/AndroidManifest.xml".replace("/", os.sep) STRING_PATH = f"launcher-game/res/values/strings.xml".replace("/", os.sep) -LAUNCER_STRING_PATH = f"LauncherCode/src/com/launchercode/LauncherStringsValue.kt".replace("/", os.sep) class ProjectUpdate(Task): @@ -181,6 +102,9 @@ class ProjectUpdate(Task): self.build_gradle_path = None def update_package_name(self): + if not self.context.update_config: + app_logger().info("配置文件没有变动,package不需要更新") + return """ 更新包名 :return: @@ -203,209 +127,6 @@ class ProjectUpdate(Task): pass pass - def update_keystore(self): - root_dir = os.path.join("game_config", self.context.package_name) - for file in os.listdir(root_dir): - if file.endswith(".keystore"): - name = file.replace(".keystore", "") - FileUtils.copy(os.path.join(root_dir, file), os.path.join(self.context.temp_project_path, file)) - - open(os.path.join(self.context.temp_project_path, "keystore.properties"), "w", encoding="utf-8").write( - f""" -keyAlias={name} -keyPassword=123456 -storeFile=./{name}.keystore -storePassword=123456 -""" - ) - return - - raise Exception("keystore not found") - - def update_config(self): - """ - 更新配置文件 - :return: - """ - root_dir = os.path.join("game_config", self.context.package_name) - config_path = list( - filter(lambda f: f.endswith(".zip") and f.startswith(self.context.package_name), os.listdir(root_dir))) - if len(config_path) <= 0: - raise Exception("config not found") - - target_path = os.path.join(root_dir, config_path[0]) - - dst = os.path.join(self.context.temp_project_path, config_path[0].replace(".zip", "")) - - result = FileUtils.decompress(target_path, dst) - app_logger().debug(f"{target_path} -> {dst} , 解压结果: {result}") - - mainly_path = os.path.join(dst, "mainly") - if not os.path.exists(mainly_path): - mainly_path = os.path.join(dst, "appConfig") - - google_services_json_path = os.path.join(dst, "google-services.json") - if not os.path.exists(google_services_json_path): - google_services_json_path = os.path.join(dst, "appConfig", "google-services.json") - - FileUtils.copy(google_services_json_path, - os.path.join(self.context.temp_project_path, "google-services.json"), - True) - - dst_path = os.path.join(self.context.temp_project_path, f"launcher-game{os.sep}assets") - - for file in list(filter(lambda f: not (f == "google_fonts.json" or f == "pag_gl_slide.pag"), - os.listdir(dst_path))): - FileUtils.delete(os.path.join(dst_path, file), True) - pass - - for file in list(filter(lambda f: f.find(".") <= 0, os.listdir(mainly_path))): - FileUtils.copy(os.path.join(mainly_path, file), os.path.join(dst_path, file)) - - with open(os.path.join(dst, "tkg_config_mainly.properties"), 'rb') as f: - self.context.config = javaproperties.load(f) - - # 不打admob - if self.context.admob_app_id is None or self.context.admob_app_id == "": - self.context.admob_app_id = self.context.get_config("admob_id") - pass - - def update_icon(self): - """ - 更新游戏Icon - :return: - """ - - target_icon_path = os.path.join("game_config", self.context.package_name, "icon.zip") - tag = "res_icon_resources" - dst = os.path.join(self.context.temp_project_path, tag) - FileUtils.decompress(target_icon_path, dst) - - for root, dirs, files in os.walk(dst): - for file in files: - temp_tart_path = os.path.join(root, file) - if temp_tart_path.find("__MACOSX") > 0: - continue - temp_dst = temp_tart_path.replace(tag, "launcher-game" + os.sep + "res") - app_logger().debug(f"copy icon = {temp_tart_path} -> {temp_dst}") - FileUtils.copy(temp_tart_path, temp_dst, True) - pass - - def update_game_result(self): - """ - 更新游戏资源 - :return: - """ - root_dir = os.path.join("game_config", self.context.package_name) - - if self.context.game_type == "unity_native": - res_path = os.path.join(root_dir, "unityLibrary.zip") - if not os.path.exists(res_path): - raise Exception("unity library not found") - dst = os.path.join(self.context.temp_project_path, "unityLibrary") - temp_dst = dst + "_res" - if os.path.exists(dst): - FileUtils.delete(dst, True) - - if os.path.exists(temp_dst): - FileUtils.delete(temp_dst, True) - - FileUtils.decompress(res_path, temp_dst) - - build_path = os.path.join(temp_dst, "build") - if os.path.exists(build_path): - FileUtils.delete(build_path, True) - - if os.listdir(temp_dst).index("unityLibrary") >= 0: - FileUtils.copy(os.path.join(temp_dst, "unityLibrary"), dst) - else: - FileUtils.copy(temp_dst, dst) - - android_manifest_xml_path = os.path.join(dst, "src", "main", "AndroidManifest.xml") - process_manifest(android_manifest_xml_path, android_manifest_xml_path) - - text = open(os.path.join(dst, "build.gradle"), "r", encoding="utf-8").read() - text = text.replace("implementation", "api") - if not 'namespace "com.unity3d.player"' in text: - text = text.replace("compileSdkVersion", """ - namespace "com.unity3d.player" - compileSdkVersion""") - - text = text.replace("unityStreamingAssets.tokenize(', ')", - '[".unity3d", ".bundle", ".version", ".bytes", ".hash"]') - text = text.replace("apply plugin: 'com.android.library'", """ -plugins { - id 'com.android.library' -} - """) - open(os.path.join(dst, "build.gradle"), "w", encoding="utf-8").write(text) - - lines = open(os.path.join(dst, "build.gradle"), "r", encoding="utf-8").readlines() - new_lines = [] - for line in lines: - if line.find("com.game:hachisdk") > 0: - continue - new_lines.append(line) - open(os.path.join(dst, "build.gradle"), "w", encoding="utf-8").writelines(new_lines) - - # 引用Unity项目 - text = open(os.path.join(self.context.temp_project_path, "ad.gradle"), "r", encoding="utf-8").read() - text = uncomment_line(text, "implementation projects.unityLibrary") - open(os.path.join(self.context.temp_project_path, "ad.gradle"), "w", encoding="utf-8").write(text) - - text = open(os.path.join(self.context.temp_project_path, "settings.gradle"), "r", encoding="utf-8").read() - text = uncomment_line(text, "include ':unityLibrary'") - open(os.path.join(self.context.temp_project_path, "settings.gradle"), "w", encoding="utf-8").write(text) - - # launcher 引用 unityActivity - text = open(os.path.join(self.context.temp_project_path, GAME_ACTIVITY_PATH), "r", encoding="utf-8").read() - text = text.replace("GLGameWebActivity", "com.unity3d.player.UnityPlayerActivity") - open(os.path.join(self.context.temp_project_path, GAME_ACTIVITY_PATH), "w", encoding="utf-8").write(text) - - text = open(os.path.join(self.context.temp_project_path, ANDROID_MANIFEST_PATH), "r", - encoding="utf-8").read() - text = text.replace("@style/LauncherGameIntroTheme", "@style/UnityThemeSelector") - open(os.path.join(self.context.temp_project_path, ANDROID_MANIFEST_PATH), "w", encoding="utf-8").write(text) - - - - else: - raise Exception(f"不支持的游戏类型 : {self.context.game_type}") - pass - - def update_image(self): - """ - 更新游戏的资源 - :return: - """ - root_dir = os.path.join("game_config", self.context.package_name) - drawable_path = os.path.join(root_dir, "drawable-xxhdpi.zip") - if not os.path.exists(drawable_path): - raise Exception("drawable not found") - dst = os.path.join(self.context.temp_project_path, "drawable_res") - FileUtils.decompress(drawable_path, dst) - - if os.path.join(dst, "drawable-xxhdpi"): - dst = os.path.join(dst, "drawable-xxhdpi") - - target_root_path = os.path.join(self.context.temp_project_path, - f"launcher-game{os.sep}res{os.sep}drawable-xxhdpi") - - image_list = list(map(lambda f: Path(f).stem, os.listdir(target_root_path))) - - for file in os.listdir(dst): - if file == ".DS_Store": - continue - temp_tar = os.path.join(dst, file) - temp_dst = os.path.join(target_root_path, file) - file_name = Path(file).stem - if file_name in image_list: - FileUtils.delete(os.path.join(target_root_path, file_name + ".png")) - FileUtils.delete(os.path.join(target_root_path, file_name + ".jpg")) - pass - FileUtils.copy(temp_tar, temp_dst, True) - pass - def update_gradle_config(self): """ 更新gradle里面的版本号 @@ -427,6 +148,9 @@ plugins { pass def update_string(self): + if not self.context.update_config: + app_logger().info("配置文件没有变动,string不需要更新") + return privacy = self.context.get_config("TkA_Url_Privacy") if not privacy or privacy == "": raise Exception("配置文件中没有配置 TkA_Url_Privacy") @@ -440,29 +164,11 @@ plugins { text = text.replace("https://doanvanquy.com/TermsOfUse.html", privacy.replace("privacy.html", "TermsOfUse.html")) open(os.path.join(self.context.temp_project_path, STRING_PATH), "w", encoding="utf-8").write(text) - - # text = open(os.path.join(self.context.temp_project_path, LAUNCER_STRING_PATH), "r", encoding="utf-8").read() - # text = text.replace("https://harmonitun.com/privacy.html", privacy) - # text = text.replace("https://harmonitun.com/TermsOfUse.html", - # privacy.replace("privacy.html", "TermsOfUse.html")) - # text = text.replace("harmounitun@outlook.com", tkg_custom) - # open(os.path.join(self.context.temp_project_path, LAUNCER_STRING_PATH), "w", encoding="utf-8").write(text) pass def execute(self): - global GAME_ACTIVITY_PATH - path = os.path.join(self.context.temp_project_path, GAME_ACTIVITY_PATH) - if not os.path.exists(path): - GAME_ACTIVITY_PATH = GAME_ACTIVITY_PATH_2 - # GAME_ACTIVITY_PATH = GAME_ACTIVITY_PATH_2 - pass self.build_gradle_path = os.path.join(self.context.temp_project_path, "build.gradle") self.update_package_name() - self.update_keystore() - self.update_config() - self.update_icon() - self.update_image() - self.update_game_result() self.update_gradle_config() self.update_string() pass diff --git a/scripts/project_update_config.py b/scripts/project_update_config.py new file mode 100644 index 0000000..ace62d4 --- /dev/null +++ b/scripts/project_update_config.py @@ -0,0 +1,71 @@ +import os +import shutil + +import javaproperties + +from scripts.task import Task +from utils import FileUtils +from utils.logger_utils import app_logger + + +class ProjectUpdateConfig(Task): + + def update_config(self): + if not self.context.update_config: + app_logger().info("配置文件没有更新") + return + + """ + 更新配置文件 + :return: + """ + + target_path = self.context.config_path + + dst = os.path.join(self.context.temp_project_path, os.path.basename(target_path).replace(".zip", "")) + + if not self.context.update_config: + app_logger().info("No update config found") + + with open(os.path.join(dst, "tkg_config_mainly.properties"), 'rb') as f: + self.context.config = javaproperties.load(f) + + # 不打admob + if self.context.admob_app_id is None or self.context.admob_app_id == "": + self.context.admob_app_id = self.context.get_config("admob_id") + return + + if os.path.exists(dst): + shutil.rmtree(dst) + + result = FileUtils.decompress(target_path, dst) + app_logger().debug(f"{target_path} -> {dst} , 解压结果: {result}") + + mainly_path = os.path.join(dst, "mainly") + if not os.path.exists(mainly_path): + mainly_path = os.path.join(dst, "appConfig") + + google_services_json_path = os.path.join(dst, "google-services.json") + if not os.path.exists(google_services_json_path): + google_services_json_path = os.path.join(dst, "appConfig", "google-services.json") + + FileUtils.copy(google_services_json_path, + os.path.join(self.context.temp_project_path, "google-services.json"), + True) + + dst_path = os.path.join(self.context.temp_project_path, f"launcher-game{os.sep}assets") + + for file in list(filter(lambda f: not (f == "google_fonts.json" or f == "pag_gl_slide.pag"), + os.listdir(dst_path))): + FileUtils.delete(os.path.join(dst_path, file), True) + pass + + for file in list(filter(lambda f: f.find(".") <= 0, os.listdir(mainly_path))): + FileUtils.copy(os.path.join(mainly_path, file), os.path.join(dst_path, file), True) + + pass + + def execute(self): + self.update_config() + self.context.save_cache_config("config", self.context.config_config_md5) + pass diff --git a/scripts/project_update_game_res.py b/scripts/project_update_game_res.py new file mode 100644 index 0000000..98162ea --- /dev/null +++ b/scripts/project_update_game_res.py @@ -0,0 +1,170 @@ +import os +import re +import xml.etree.ElementTree as ET +from xml.dom import minidom + +from scripts.task import Task +from utils import FileUtils +from utils.logger_utils import app_logger + +GAME_ACTIVITY_PATH = f"launcher-game/src/com/game/launcher/activity/GLGameActivity.kt".replace("/", os.sep) +ANDROID_MANIFEST_PATH = f"launcher-game/AndroidManifest.xml".replace("/", os.sep) + + +def process_manifest(input_path, output_path): + # 解析XML文件 + tree = ET.parse(input_path) + root = tree.getroot() + + # 定义命名空间映射 + android_namespace = 'http://schemas.android.com/apk/res/android' + ET.register_namespace('android', android_namespace) + namespaces = {'android': android_namespace} + + # 处理application标签,移除所有属性 + application = root.find('application') + if application is not None: + # 保存所有子节点 + children = list(application) + # 清除application标签 + root.remove(application) + # 创建新的application标签(无属性) + new_application = ET.Element('application') + # 添加回所有子节点 + for child in children: + new_application.append(child) + # 将新的application标签添加回root + root.append(new_application) + + # 查找并删除指定的activity节点 + activity_to_remove = new_application.find( + ".//activity[@android:name='com.unity3d.player.UnityPlayerActivity']", + namespaces=namespaces + ) + if activity_to_remove is not None: + new_application.remove(activity_to_remove) + + # 保存处理后的XML,保留android命名空间前缀 + rough_string = ET.tostring(root, 'utf-8') + reparsed = minidom.parseString(rough_string) + pretty_xml = reparsed.toprettyxml(indent=" ", encoding='utf-8') + + # 去除空行 + lines = pretty_xml.decode('utf-8').split('\n') + non_empty_lines = [line for line in lines if line.strip() != ''] + pretty_xml = '\n'.join(non_empty_lines).encode('utf-8') + + with open(output_path, 'wb') as f: + f.write(pretty_xml) + + print(f"处理完成,结果已保存到 {output_path}") + + +def uncomment_line(content, line_pattern): + """ + 取消指定行的注释 + + :param content: 文件内容 + :param line_pattern: 要取消注释的行内容(不含前导//和空格) + :return: 更新后的内容 + """ + # 匹配以//开头,后跟任意空格,然后是目标行内容 + pattern = rf'^(\s*)//\s*({re.escape(line_pattern)}\s*)$' + + # 替换为去注释版本(保留原有缩进) + replacement = rf'\1\2' + + updated_content = re.sub(pattern, replacement, content, flags=re.MULTILINE) + return updated_content + + +class ProjectUpdateGameRes(Task): + + def update_game_result(self): + if not self.context.update_res_unity: + app_logger().info("No update game res found") + return + """ + 更新游戏资源 + :return: + """ + if self.context.game_type == "unity_native": + res_path = self.context.res_unity_path + dst = os.path.join(self.context.temp_project_path, "unityLibrary") + temp_dst = dst + "_res" + if os.path.exists(dst): + result = FileUtils.delete(dst, True) + app_logger().info(f"删除unityLibrary结果 : {result}") + + if os.path.exists(temp_dst): + result = FileUtils.delete(temp_dst, True) + app_logger().info(f"删除temp unityLibrary结果 : {result}") + + FileUtils.decompress(res_path, temp_dst) + + build_path = os.path.join(temp_dst, "build") + if os.path.exists(build_path): + FileUtils.delete(build_path, True) + + if os.listdir(temp_dst).index("unityLibrary") >= 0: + FileUtils.copy(os.path.join(temp_dst, "unityLibrary"), dst) + else: + FileUtils.copy(temp_dst, dst) + + android_manifest_xml_path = os.path.join(dst, "src", "main", "AndroidManifest.xml") + process_manifest(android_manifest_xml_path, android_manifest_xml_path) + + text = open(os.path.join(dst, "build.gradle"), "r", encoding="utf-8").read() + text = text.replace("implementation", "api") + if not 'namespace "com.unity3d.player"' in text: + text = text.replace("compileSdkVersion", """ + namespace "com.unity3d.player" + compileSdkVersion""") + + text = text.replace("unityStreamingAssets.tokenize(', ')", + '[".unity3d", ".bundle", ".version", ".bytes", ".hash"]') + text = text.replace("apply plugin: 'com.android.library'", """ + plugins { + id 'com.android.library' + } + """) + open(os.path.join(dst, "build.gradle"), "w", encoding="utf-8").write(text) + + lines = open(os.path.join(dst, "build.gradle"), "r", encoding="utf-8").readlines() + new_lines = [] + for line in lines: + if line.find("com.game:hachisdk") > 0: + continue + new_lines.append(line) + open(os.path.join(dst, "build.gradle"), "w", encoding="utf-8").writelines(new_lines) + + # 引用Unity项目 + text = open(os.path.join(self.context.temp_project_path, "ad.gradle"), "r", encoding="utf-8").read() + text = uncomment_line(text, "implementation projects.unityLibrary") + open(os.path.join(self.context.temp_project_path, "ad.gradle"), "w", encoding="utf-8").write(text) + + text = open(os.path.join(self.context.temp_project_path, "settings.gradle"), "r", encoding="utf-8").read() + text = uncomment_line(text, "include ':unityLibrary'") + open(os.path.join(self.context.temp_project_path, "settings.gradle"), "w", encoding="utf-8").write(text) + + # launcher 引用 unityActivity + text = open(os.path.join(self.context.temp_project_path, GAME_ACTIVITY_PATH), "r", encoding="utf-8").read() + text = text.replace("GLGameWebActivity", "com.unity3d.player.UnityPlayerActivity") + open(os.path.join(self.context.temp_project_path, GAME_ACTIVITY_PATH), "w", encoding="utf-8").write(text) + + text = open(os.path.join(self.context.temp_project_path, ANDROID_MANIFEST_PATH), "r", + encoding="utf-8").read() + text = text.replace("@style/LauncherGameIntroTheme", "@style/UnityThemeSelector") + open(os.path.join(self.context.temp_project_path, ANDROID_MANIFEST_PATH), "w", encoding="utf-8").write(text) + + + + else: + raise Exception(f"不支持的游戏类型 : {self.context.game_type}") + pass + + def execute(self): + self.update_game_result() + + self.context.save_cache_config("res_unity", self.context.config_res_unity_md5) + pass diff --git a/scripts/project_update_icon.py b/scripts/project_update_icon.py new file mode 100644 index 0000000..67ff66a --- /dev/null +++ b/scripts/project_update_icon.py @@ -0,0 +1,39 @@ +import os +import shutil + +from scripts.task import Task +from utils import FileUtils +from utils.logger_utils import app_logger + + +class ProjectUpdateIcon(Task): + + def update_icon(self): + """ + 更新游戏Icon + :return: + """ + + if not self.context.update_res_icon: + app_logger().info("No update res icon found") + return + tag = "res_icon_resources" + dst = os.path.join(self.context.temp_project_path, tag) + if os.path.exists(dst): + shutil.rmtree(dst) + + FileUtils.decompress(self.context.res_icon_path, dst) + + for root, dirs, files in os.walk(dst): + for file in files: + temp_tart_path = os.path.join(root, file) + if temp_tart_path.find("__MACOSX") > 0: + continue + temp_dst = temp_tart_path.replace(tag, "launcher-game" + os.sep + "res") + app_logger().debug(f"copy icon = {temp_tart_path} -> {temp_dst}") + FileUtils.copy(temp_tart_path, temp_dst, True) + pass + + def execute(self): + self.update_icon() + self.context.save_cache_config("res_icon", self.context.config_res_icon_md5) diff --git a/scripts/project_update_image.py b/scripts/project_update_image.py new file mode 100644 index 0000000..195019f --- /dev/null +++ b/scripts/project_update_image.py @@ -0,0 +1,52 @@ +import os +import shutil +from pathlib import Path + +from scripts.task import Task +from utils import FileUtils +from utils.logger_utils import app_logger + + +class ProjectUpdateImage(Task): + + def update_image(self): + """ + 更新游戏的资源 + :return: + """ + + if not self.context.update_res_img: + app_logger().info("No update image found") + return + dst = os.path.join(self.context.temp_project_path, "drawable_res") + if os.path.exists(dst): + shutil.rmtree(dst) + + FileUtils.decompress(self.context.res_img_path, dst) + + if os.path.join(dst, "drawable-xxhdpi"): + dst = os.path.join(dst, "drawable-xxhdpi") + + target_root_path = os.path.join(self.context.temp_project_path, + f"launcher-game{os.sep}res{os.sep}drawable-xxhdpi") + + image_list = list(map(lambda f: Path(f).stem, os.listdir(target_root_path))) + + for file in os.listdir(dst): + if file == ".DS_Store": + continue + res_name = Path(file).stem + file_name = self.context.get_map_from_key(res_name) + temp_tar = os.path.join(dst, file) + temp_dst = os.path.join(target_root_path, file).replace(file_name, self.context.get_map_from_key(res_name)) + if file_name in image_list: + FileUtils.delete(os.path.join(target_root_path, file_name + ".png")) + FileUtils.delete(os.path.join(target_root_path, file_name + ".jpg")) + pass + app_logger().info(f"copy {temp_tar} => {temp_dst}") + FileUtils.copy(temp_tar, temp_dst, True) + pass + + def execute(self): + self.update_image() + self.context.save_cache_config("res_img", self.context.config_res_img_md5) diff --git a/scripts/project_update_keystore.py b/scripts/project_update_keystore.py new file mode 100644 index 0000000..24a2cdf --- /dev/null +++ b/scripts/project_update_keystore.py @@ -0,0 +1,41 @@ +import os.path + +from scripts.task import Task +from utils import FileUtils +from utils.logger_utils import app_logger + + +class ProjectUpdateKeystore(Task): + + def update_keystore(self): + if not self.context.update_keystore: + app_logger().info("No update_keystore") + return + + name = os.path.basename(self.context.keystore_path).replace(".keystore", "") + target_path = os.path.join(self.context.temp_project_path, name + ".keystore") + + if os.path.exists(target_path): + os.remove(target_path) + + result = FileUtils.copy(self.context.keystore_path, target_path) + + app_logger().debug(f"copy keystore result {result}") + + target_path = os.path.join(self.context.temp_project_path, "keystore.properties") + if os.path.exists(target_path): + os.remove(target_path) + + open(target_path, "w", encoding="utf-8").write( + f""" +keyAlias={name} +keyPassword=123456 +storeFile=./{name}.keystore +storePassword=123456 +""" + ) + + def execute(self): + self.update_keystore() + self.context.save_cache_config("keystore", self.context.config_keystore_md5) + pass diff --git a/scripts/task.py b/scripts/task.py index 68b47ff..bc03242 100644 --- a/scripts/task.py +++ b/scripts/task.py @@ -5,7 +5,6 @@ class Task: def __init__(self, context: Context): self.context = context - pass def execute(self): pass