#!/usr/bin/env python3 """Статические проверки без запуска лаунчера. Ненулевой код возврата при ошибке. Запуск: python3 tests/scripts/static_checks.py <папка сборки с BILAUNCHER_BUILD_TESTS=ON> 1. tools/check_translations.py — каталог переводов цел и совпадает с кодом. 2. qmllint по модулю BiLauncher (цель BiLauncher_qmllint, её создаёт qt_add_qml_module). В коде уже есть предупреждения, а код ради тестов не правится, поэтому проверка работает «храповиком»: падает, только если предупреждений стало больше, чем записано в tests/qmllint-baseline.txt. Стало меньше — просит опустить планку, чтобы достигнутое не откатилось. 3. CHANGELOG.md содержит раздел для версии из project(BiLauncher VERSION …): без него release.yml создаст релиз с пустым описанием (только предупреждение). 4. Скрипты .ps1 с не-ASCII символами сохранены в UTF-8 с BOM: без BOM Windows PowerShell 5.1 читает их в ANSI-кодировке и падает на разборе кириллицы. """ import glob, os, re, subprocess, sys ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) BASELINE = os.path.join(ROOT, "tests", "qmllint-baseline.txt") def step(title): print(f"\n=== {title}", flush=True) def check_translations(): step("Каталог переводов") return subprocess.call([sys.executable, os.path.join(ROOT, "tools", "check_translations.py")]) == 0 def check_qmllint(build_dir): step("qmllint (BiLauncher_qmllint)") cmake = os.environ.get("CMAKE", "cmake") proc = subprocess.run([cmake, "--build", build_dir, "--target", "BiLauncher_qmllint"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, encoding="utf-8", errors="replace") output = proc.stdout with open(os.path.join(build_dir, "qmllint.log"), "w", encoding="utf-8") as log: log.write(output) warnings = [line for line in output.splitlines() if line.startswith("Warning:")] errors = [line for line in output.splitlines() if line.startswith("Error:")] for line in warnings + errors: print(line) baseline = int(open(BASELINE, encoding="utf-8").read().split()[0]) print(f"\nпредупреждений: {len(warnings)}, ошибок: {len(errors)}, планка: {baseline}" f" (полный вывод — {os.path.join(build_dir, 'qmllint.log')})") if proc.returncode != 0 and not warnings and not errors: print("qmllint не запустился — см. лог") return False if errors: print("ОШИБКА: qmllint нашёл ошибки") return False if len(warnings) > baseline: print(f"ОШИБКА: предупреждений стало больше ({len(warnings)} > {baseline}). " "Исправьте новые или, если они осознанные, поднимите tests/qmllint-baseline.txt.") return False if len(warnings) < baseline: print(f"Хорошо: предупреждений меньше планки — опустите tests/qmllint-baseline.txt до {len(warnings)}.") return True def check_changelog(): step("CHANGELOG.md и версия проекта") cmake_text = open(os.path.join(ROOT, "CMakeLists.txt"), encoding="utf-8").read() match = re.search(r"^project\(BiLauncher VERSION ([0-9]+\.[0-9]+\.[0-9]+)", cmake_text, re.M) if not match: print("ОШИБКА: не найден project(BiLauncher VERSION x.y.z)") return False version = match.group(1) changelog = open(os.path.join(ROOT, "CHANGELOG.md"), encoding="utf-8").read() if re.search(r"^## \[" + re.escape(version) + r"(-rc[0-9]+)?\]", changelog, re.M): print(f"версия {version}: раздел в CHANGELOG.md есть") else: print(f"ПРЕДУПРЕЖДЕНИЕ: в CHANGELOG.md нет раздела ## [{version}] — описание релиза будет пустым") return True def check_ps1_bom(): step("Кодировка .ps1") ok = True for path in glob.glob(os.path.join(ROOT, "**", "*.ps1"), recursive=True): if os.path.relpath(path, ROOT).startswith("build"): # папки сборки continue data = open(path, "rb").read() if not data.startswith(b"\xef\xbb\xbf") and any(b > 0x7F for b in data): print(f"ОШИБКА: {os.path.relpath(path, ROOT)} содержит не-ASCII, но сохранён без UTF-8 BOM") ok = False if ok: print("все .ps1 читаются Windows PowerShell 5.1") return ok def main(): if len(sys.argv) != 2: print(__doc__) return 2 build_dir = os.path.abspath(sys.argv[1]) results = [check_translations(), check_qmllint(build_dir), check_changelog(), check_ps1_bom()] print("\nИТОГ:", "OK" if all(results) else "ЕСТЬ ОШИБКИ") return 0 if all(results) else 1 if __name__ == "__main__": sys.exit(main())