mirror of
https://github.com/NewFuture/DDNS.git
synced 2026-10-20 19:00:34 +00:00
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
250 lines
8.1 KiB
Python
Executable File
250 lines
8.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
import os
|
|
import re
|
|
import urllib.request
|
|
import time
|
|
import json
|
|
from datetime import datetime, timezone
|
|
|
|
ROOT = "."
|
|
init_py_path = os.path.join(ROOT, "ddns", "__init__.py")
|
|
|
|
# 匹配 try-except 块,去除导入前缩进,保证import顶格,删除的行用空行代替
|
|
PATTERN = re.compile(
|
|
r"^[ \t]*try:[^\n]*python 3[^\n]*\n" # try: # python 3
|
|
r"((?:[ \t]+[^\n]*\n)+?)" # python3 导入内容
|
|
r"^[ \t]*except ImportError:[^\n]*\n" # except ImportError: # python 2
|
|
r"((?:[ \t]+from[^\n]*\n|[ \t]+import[^\n]*\n)*)", # except块内容
|
|
re.MULTILINE,
|
|
)
|
|
|
|
|
|
def dedent_imports_with_blank(import_block, try_block, except_block):
|
|
"""
|
|
保留python3导入并去除缩进,try/except及except内容用空行代替
|
|
"""
|
|
try_lines = try_block.count("\n")
|
|
except_lines = except_block.count("\n")
|
|
imports = "".join(line.lstrip() for line in import_block.splitlines(keepends=True))
|
|
return ("\n" * try_lines) + imports + ("\n" * except_lines)
|
|
|
|
|
|
def extract_pure_version(version_str):
|
|
"""
|
|
提取前4组数字并用点拼接,如 v1.2.3.beta4.5 -> 1.2.3.4
|
|
"""
|
|
nums = re.findall(r"\d+", version_str)
|
|
return ".".join(nums[:4]) if nums else "0.0.0"
|
|
|
|
|
|
def update_nuitka_version(pyfile, version=None):
|
|
"""
|
|
读取 __version__ 并替换 nuitka-project 版本号
|
|
"""
|
|
pure_version = extract_pure_version(version)
|
|
|
|
with open(pyfile, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
# 替换 nuitka-project 行
|
|
new_content, n = re.subn(
|
|
r"(# nuitka-project: --product-version=)[^\n]*", r"\g<1>" + pure_version, content
|
|
)
|
|
if n > 0:
|
|
with open(pyfile, "w", encoding="utf-8") as f:
|
|
f.write(new_content)
|
|
print(f"update nuitka-project version: {pure_version} in {pyfile}")
|
|
return True
|
|
return False
|
|
|
|
|
|
def add_nuitka_file_description(pyfile):
|
|
"""
|
|
添加 --file-description 配置,使用 __description__ 变量的值
|
|
"""
|
|
with open(init_py_path, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
# 提取 __description__ 变量的值
|
|
desc_match = re.search(r'__description__\s*=\s*[\'"]([^\'"]+)[\'"]', content)
|
|
if not desc_match:
|
|
print(f"No __description__ found in {init_py_path}")
|
|
return False
|
|
description = desc_match.group(1)
|
|
description_line = f'\n# nuitka-project: --file-description="{description}"\n'
|
|
with open(pyfile, "a", encoding="utf-8") as f:
|
|
f.write(description_line)
|
|
return True
|
|
|
|
|
|
def add_nuitka_include_modules(pyfile):
|
|
"""
|
|
读取 dns 目录下的所有 Python 模块,并添加到 run.py 末尾
|
|
"""
|
|
dns_dir = os.path.join(ROOT, "ddns/provider")
|
|
if not os.path.exists(dns_dir):
|
|
print(f"DNS directory not found: {dns_dir}")
|
|
return False
|
|
|
|
# 获取所有 Python 模块文件
|
|
modules = []
|
|
for filename in os.listdir(dns_dir):
|
|
if filename.endswith(".py") and filename != "__init__.py":
|
|
module_name = filename[:-3] # 去掉 .py 扩展名
|
|
modules.append(f"ddns.provider.{module_name}")
|
|
|
|
if not modules:
|
|
print("No DNS modules found")
|
|
return False
|
|
|
|
# 直接在文件末尾追加配置行
|
|
with open(pyfile, "a", encoding="utf-8") as f:
|
|
for module in sorted(modules):
|
|
f.write(f"# nuitka-project: --include-module={module}\n")
|
|
|
|
print(f'Added {len(modules)} DNS modules to {pyfile}: {", ".join(modules)}')
|
|
return True
|
|
|
|
|
|
def remove_python2_compatibility(pyfile):
|
|
"""
|
|
自动将所有 try-except python2/3 兼容导入替换为 python3 only 导入,并显示处理日志
|
|
删除指定文件中的 python2 兼容代码,逐行处理
|
|
"""
|
|
with open(pyfile, "r", encoding="utf-8") as f:
|
|
lines = f.readlines()
|
|
|
|
new_lines = []
|
|
i = 0
|
|
changed = False
|
|
while i < len(lines):
|
|
line = lines[i]
|
|
# 匹配 "try: # python3" 或 "try: # python 3"
|
|
if re.match(r"^[ \t]*try:[^\n]*python ?3", line):
|
|
try_block = []
|
|
except_block = []
|
|
i += 1
|
|
# 收集try块内容
|
|
while i < len(lines) and lines[i].startswith((" ", "\t")):
|
|
try_block.append(lines[i].lstrip())
|
|
i += 1
|
|
# 跳过空行
|
|
while i < len(lines) and lines[i].strip() == "":
|
|
i += 1
|
|
# 检查是否存在except块 (不检查具体错误类型,但必须包含python2或python 2)
|
|
if i < len(lines) and re.match(r"^[ \t]*except[^\n]*python ?2", lines[i]):
|
|
i += 1
|
|
# 收集except块内容
|
|
except_block = []
|
|
while i < len(lines) and lines[i].startswith((" ", "\t")):
|
|
except_block.append(lines[i])
|
|
i += 1
|
|
# 添加try块内容,except块用空行替代
|
|
new_lines.extend(["\n"] + try_block + ["\n"] * (len(except_block) + 1))
|
|
changed = True
|
|
else:
|
|
# 没有except块,原样保留
|
|
new_lines.append(line)
|
|
new_lines.extend(try_block)
|
|
else:
|
|
new_lines.append(line)
|
|
i += 1
|
|
|
|
if changed:
|
|
with open(pyfile, "w", encoding="utf-8") as f:
|
|
f.writelines(new_lines)
|
|
print(f"Removed python2 compatibility from {pyfile}")
|
|
|
|
|
|
def get_latest_tag():
|
|
url = "https://api.github.com/repos/NewFuture/DDNS/tags?per_page=1"
|
|
try:
|
|
with urllib.request.urlopen(url) as response:
|
|
data = json.load(response)
|
|
if data and isinstance(data, list):
|
|
return data[0]['name'] # 获取第一个 tag 的 name
|
|
except Exception as e:
|
|
print("Error fetching tag:", e)
|
|
return None
|
|
|
|
|
|
def normalize_tag(tag: str) -> str:
|
|
v = tag.lower().lstrip("v")
|
|
v = re.sub(r"-beta(\d*)", r"b\1", v)
|
|
v = re.sub(r"-alpha(\d*)", r"a\1", v)
|
|
v = re.sub(r"-rc(\d*)", r"rc\1", v)
|
|
return v
|
|
|
|
|
|
def ten_minute_bucket_id():
|
|
epoch_minutes = int(time.time() // 60) # 当前时间(分钟级)
|
|
bucket = epoch_minutes // 10 # 每10分钟为一个 bucket
|
|
return bucket % 65536 # 限制在 0~65535 (2**16)
|
|
|
|
|
|
def generate_version():
|
|
ref = os.environ.get('GITHUB_REF_NAME', '')
|
|
if re.match(r"^v\d+\.\d+", ref):
|
|
return normalize_tag(ref)
|
|
|
|
base = "4.0.0"
|
|
suffix = ten_minute_bucket_id()
|
|
if ref == "master" or ref == "main":
|
|
tag = get_latest_tag()
|
|
if tag:
|
|
base = normalize_tag(tag)
|
|
|
|
return f"{base}.dev{suffix}"
|
|
|
|
|
|
def replace_version_and_date(pyfile: str, version: str, date_str: str):
|
|
with open(pyfile, 'r', encoding="utf-8") as f:
|
|
text = f.read()
|
|
text = text.replace("${BUILD_VERSION}", version)
|
|
text = text.replace("${BUILD_DATE}", date_str)
|
|
if text is not None:
|
|
with open(pyfile, 'w', encoding="utf-8") as f:
|
|
f.write(text)
|
|
print(f"Updated {pyfile}: version={version}, date={date_str}")
|
|
else:
|
|
exit(1)
|
|
|
|
|
|
def main():
|
|
"""
|
|
遍历所有py文件并替换兼容导入,同时更新nuitka版本号
|
|
"""
|
|
if len(sys.argv) > 1 and sys.argv[1].lower() != 'version':
|
|
print(f'unknown arguments: {sys.argv}')
|
|
exit(1)
|
|
version = generate_version()
|
|
date_str = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
print(f"Version: {version}")
|
|
print(f"Date: {date_str}")
|
|
|
|
# 修改__init__.py 中的 __version__
|
|
replace_version_and_date(init_py_path, version, date_str)
|
|
if len(sys.argv) > 1 and sys.argv[1].lower() == 'version':
|
|
# python version only
|
|
exit(0)
|
|
|
|
run_py_path = os.path.join(ROOT, "run.py")
|
|
update_nuitka_version(run_py_path, version)
|
|
add_nuitka_file_description(run_py_path)
|
|
add_nuitka_include_modules(run_py_path)
|
|
|
|
changed_files = 0
|
|
for dirpath, _, filenames in os.walk(ROOT):
|
|
for fname in filenames:
|
|
if fname.endswith(".py"):
|
|
fpath = os.path.join(dirpath, fname)
|
|
remove_python2_compatibility(fpath)
|
|
changed_files += 1
|
|
print("done")
|
|
print(f"Total processed files: {changed_files}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|