auto_build_launcher/scripts/project_update.py

397 lines
16 KiB
Python

import os.path
from pathlib import Path
import re
import javaproperties
import requests
from lxml import etree
from scripts.context import Context
from scripts.task import Task
from utils import FileUtils
from utils.logger_utils import app_logger
def update_dependency_version(content, dependency_name, new_version):
"""
更新 Gradle 依赖的版本号
:param content: Gradle 文件内容
:param dependency_name: 依赖名称(可以是完整字符串或部分匹配)
:param new_version: 新版本号
:return: 更新后的内容
"""
# 匹配 implementation 声明,捕获组用于保留前缀和后缀
pattern = rf"(implementation\s*\(\s*['\"]{re.escape(dependency_name)}:)([^'\"]+)(['\"]\s*\))"
# 替换版本号部分
updated_content = re.sub(pattern, rf"\g<1>{new_version}\g<3>", content)
return updated_content
def get_latest_version(url):
try:
response = requests.get(url)
root = etree.fromstring(response.content)
latest = root.xpath("//latest/text()") or root.xpath("//version[last()]/text()")
return latest[0] if latest else None
except Exception as e:
app_logger().error(f"Error: {e}")
return None
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 变量值
:param content: Gradle 文件内容
:param variable_name: 变量名 (如 "releaseName")
:param new_value: 新值 (字符串或数字)
:return: 更新后的内容
"""
# 处理字符串值(带引号)
if isinstance(new_value, str):
# 匹配带引号的字符串赋值
pattern = rf'(final\s+def\s+{re.escape(variable_name)}\s*=\s*["\'])(.*?)(["\'])'
replacement = rf'\g<1>{new_value}\g<3>'
else:
# 匹配数字值(不带引号)
pattern = rf'(final\s+def\s+{re.escape(variable_name)}\s*=\s*)(\d+)'
replacement = rf'\g<1>{new_value}'
updated_content = re.sub(pattern, replacement, content)
return updated_content
def update_gradle_property(content, key, new_value):
# 匹配两种格式:
# 1. resValue "string", "key", "value"
# 2. resValue("string", "key", "value")
pattern = rf'(resValue\s*\(?\s*["\']string["\']\s*,\s*["\']{re.escape(key)}["\']\s*,\s*["\'])(.*?)(["\']\s*\)?)'
# 替换为新值
updated_content = re.sub(pattern, rf'\g<1>{new_value}\g<3>', content)
return updated_content
GAME_ACTIVITY_PATH = f"LauncherCode/src/com/launchercode/GameActivity.kt".replace("/", os.sep)
ANDROID_MANIFEST_PATH = f"lawnchair/AndroidManifest.xml".replace("/", os.sep)
STRING_PATH = f"LauncherCode/res/values/strings.xml".replace("/", os.sep)
LAUNCER_STRING_PATH = f"LauncherCode/src/com/launchercode/LauncherStringsValue.kt".replace("/", os.sep)
class ProjectUpdate(Task):
def __init__(self, context: Context):
super().__init__(context)
self.build_gradle_path = None
def update_package_name(self):
"""
更新包名
:return:
"""
build_gradle_path = os.path.join(self.context.temp_project_path, "build.gradle")
text = open(build_gradle_path, "r", encoding="utf-8").read()
text = text.replace("com.fingerheart.launcher.game.free.sdjws", self.context.package_name)
open(build_gradle_path, "w", encoding="utf-8").write(text)
xml_path = os.path.join(self.context.temp_project_path, "lawnchair/res/xml")
# com.launchercode.SplashActivity
# TODO 这里还需要改启动项
for root, dirs, files in os.walk(xml_path):
for file in files:
temp_xml_path = os.path.join(root, file)
text = open(temp_xml_path, "r", encoding="utf-8").read()
text = text.replace("com.fingerheart.launcher.game.free.sdjws", self.context.package_name)
open(temp_xml_path, "w", encoding="utf-8").write(text)
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")
google_services_json_path = os.path.join(dst, "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"lawnchair{os.sep}assets")
for file in list(filter(lambda f: f != "google_fonts.json", 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(mainly_path, "tkg_config_mainly.properties"), 'rb') as f:
self.context.config = javaproperties.load(f)
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, "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)
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)
text = open(os.path.join(dst, "build.gradle"), "r", encoding="utf-8").read()
text = text.replace("implementation", "api")
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)
# 引用Unity项目
text = open(self.build_gradle_path, "r", encoding="utf-8").read()
text = uncomment_line(text, "implementation projects.unityLibrary")
open(self.build_gradle_path, "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("WebActivity", "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"LauncherCode{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):
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里面的版本号
:return:
"""
build_gradle_path = os.path.join(self.context.temp_project_path, "build.gradle")
text = open(build_gradle_path, "r", encoding="UTF-8").read()
text = update_gradle_property(text, "admob_app_id", self.context.admob_app_id)
text = update_gradle_property(text, "game_services_project_id", self.context.game_services_project_id)
text = update_gradle_property(text, "facebook_app_id", self.context.facebook_app_id)
text = update_gradle_property(text, "facebook_client_token", self.context.facebook_client_token)
text = update_gradle_property(text, "derived_app_name", self.context.get_app_name())
text = update_gradle_variable(text, "versionDisplayName", self.context.version_display_name)
text = update_gradle_variable(text, "version_code", self.context.version_code)
text = update_dependency_version(text, f"straw:hachisdk_unity_{self.context.package_name}",
self.context.sdk_prolink_version)
text = update_dependency_version(text, f"com.game:hachisdk_unity_{self.context.package_name}",
self.context.sdk_version)
open(build_gradle_path, "w", encoding="UTF-8").write(text)
pass
def update_string(self):
privacy = self.context.get_config("TkA_Url_Privacy")
if not privacy or privacy == "":
raise Exception("配置文件中没有配置 TkA_Url_Privacy")
tkg_custom = self.context.get_config("tkg_custom")
if not tkg_custom or tkg_custom == "":
raise Exception("配置文件中没有配置 tkg_custom")
text = open(os.path.join(self.context.temp_project_path, 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, 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):
self.build_gradle_path = os.path.join(self.context.temp_project_path, "build.gradle")
self.init_sdk_version()
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
def get_sdk_version(self) -> str:
for i in range(3):
try:
url = f"https://repo.dgtverse.cn/repository/tk_my/com/game/hachisdk_unity_{self.context.package_name}/maven-metadata.xml"
version = get_latest_version(url)
if version:
return version
pass
except Exception as e:
pass
raise Exception("sdk error.")
def get_prolink_version(self) -> str:
for i in range(3):
try:
url = f"https://repo.dgtverse.cn/repository/tk_my/straw/hachisdk_unity_{self.context.package_name}/maven-metadata.xml"
version = get_latest_version(url)
if version:
return version
except Exception as e:
pass
raise Exception("sdk prolink error.")
def init_sdk_version(self):
self.context.sdk_version = self.get_sdk_version()
self.context.sdk_prolink_version = self.get_prolink_version()
pass