#!/usr/bin/env python3 # version_code_manager.py # 用途:管理各渠道的 Android versionCode / iOS buildNumber 自动递增 # # 逻辑(优先级从高到低): # 1. 读取 Jenkins node 状态文件 ~/.nld_build_state/{channel}.json 中保存的上次值 # 2. 若状态文件不存在,则从 ChannelSetting.yaml 的 BuildCode 字段读取默认初始值 # 3. 将该值 +1 后: # a. 写回状态文件(跨构建持久化,因为 Jenkins 每次构建是全新进程,环境变量不保留) # b. 输出 shell export 语句供调用方 source # # 使用方式(在 build_unity.sh 中): # source <(python3 .../version_code_manager.py ) # echo "VERSION_CODE=$NLD_CLIENT__VERSION_CODE" # # 传入参数: # argv[1]: ChannelName (e.g. "Dev") # argv[2]: ChannelYamlPath (e.g. "/path/to/Tool/Channels/Dev/ChannelSetting.yaml") # # 退出码: # 0: 成功 # 1: 参数不足 # 2: yaml 文件不存在 # 3: yaml 解析失败 # 4: BuildCode 字段不存在 import sys import os import json import yaml # Jenkins node 上存储各渠道 versionCode 的状态目录 # 优先使用 JENKINS_HOME 下的固定路径,避免 ~ 在不同用户身份下解析到不同目录 # 发布历史只保存在 Jenkins node 上,不进入 git 仓库 _jenkins_home = os.environ.get("JENKINS_HOME") or os.environ.get("HUDSON_HOME") if _jenkins_home: STATE_DIR = os.path.join(_jenkins_home, "nld_build_state") else: # 本地调试场景回退到 ~/.nld_build_state STATE_DIR = os.path.expanduser("~/.nld_build_state") def main(): if len(sys.argv) < 3: print("Usage: python3 version_code_manager.py ", file=sys.stderr) sys.exit(1) channel_name = sys.argv[1] yaml_path = sys.argv[2] # 环境变量名称,将渠道名中的特殊字符替换为下划线,保持规范 safe_channel = channel_name.replace("-", "_").replace(" ", "_") env_var_name = f"NLD_CLIENT_{safe_channel}_VERSION_CODE" # 1. 优先从状态文件读取(跨构建持久化存储) current_code = read_state_file(safe_channel) if current_code is not None: print(f"[VersionCode] 从状态文件读取到 {safe_channel} 上次值: {current_code}", file=sys.stderr) else: # 2. 回退到 YAML 默认值 print(f"[VersionCode] 状态文件不存在,从 YAML 读取初始值", file=sys.stderr) current_code = read_build_code_from_yaml(yaml_path) # 2. 递增 new_code = current_code + 1 print(f"[VersionCode] 渠道={channel_name}, 旧值={current_code}, 新值={new_code}", file=sys.stderr) # 3. 写回状态文件(跨构建持久化) write_state_file(safe_channel, new_code) # 4. 输出 export 语句供 shell source(写到 stdout) print(f"export {env_var_name}={new_code}") print(f"export NLD_CURRENT_VERSION_CODE={new_code}") def read_state_file(safe_channel): """从 Jenkins node 状态文件读取渠道的上次 versionCode,不存在时返回 None""" state_file = os.path.join(STATE_DIR, f"{safe_channel}.json") if not os.path.exists(state_file): print(f"[VersionCode] 状态文件不存在: {state_file}", file=sys.stderr) return None try: with open(state_file, 'r', encoding='utf-8') as f: data = json.load(f) code = int(data.get("version_code", "")) return code except Exception as e: print(f"[VersionCode] 警告: 读取状态文件失败: {e}", file=sys.stderr) return None def write_state_file(safe_channel, new_code): """将递增后的 versionCode 写入 Jenkins node 状态文件,实现跨构建持久化""" try: os.makedirs(STATE_DIR, exist_ok=True) state_file = os.path.join(STATE_DIR, f"{safe_channel}.json") with open(state_file, 'w', encoding='utf-8') as f: json.dump({"version_code": new_code, "channel": safe_channel}, f) print(f"[VersionCode] ✅ 已将 {safe_channel} versionCode={new_code} 写入状态文件: {state_file}", file=sys.stderr) except Exception as e: print(f"[VersionCode] 警告: 写入状态文件失败: {e},不影响本次构建", file=sys.stderr) def read_build_code_from_yaml(yaml_path): """从 ChannelSetting.yaml 读取 BuildCode 字段""" if not os.path.exists(yaml_path): print(f"[VersionCode] 错误: YAML 文件不存在: {yaml_path}", file=sys.stderr) sys.exit(2) try: with open(yaml_path, 'r', encoding='utf-8') as f: data = yaml.safe_load(f) except yaml.YAMLError as e: print(f"[VersionCode] 错误: 解析 YAML 失败: {e}", file=sys.stderr) sys.exit(3) build_code = data.get("BuildCode") if build_code is None: print(f"[VersionCode] 错误: ChannelSetting.yaml 中不存在 BuildCode 字段", file=sys.stderr) sys.exit(4) try: code = int(build_code) except (ValueError, TypeError): print(f"[VersionCode] 错误: BuildCode 值 '{build_code}' 无法解析为整数", file=sys.stderr) sys.exit(4) print(f"[VersionCode] 从 YAML 读取到 BuildCode 初始值: {code}", file=sys.stderr) return code if __name__ == "__main__": main()