"""Package an independently built debug APK without overwriting an old delivery.""" import argparse import hashlib import json from pathlib import Path import re import shutil import subprocess import tomllib def digest(path): return hashlib.sha256(path.read_bytes()).hexdigest() def main(): parser = argparse.ArgumentParser() parser.add_argument('--build', type=Path, required=True) parser.add_argument('--sdk', type=Path, required=True) args = parser.parse_args() root = Path(__file__).resolve().parent source = root.parent / '安卓程序源代码' config = (source / 'androidApp/build.gradle.kts').read_text(encoding='utf-8') version = re.search(r'versionName = "([^"]+)"', config)[1] code = int(re.search(r'versionCode = (\d+)', config)[1]) entries = [] for path in sorted(source.rglob('*')): relative = path.relative_to(source) if not path.is_file() or any(p in {'build', '.gradle', '.kotlin'} for p in relative.parts) or path.name == 'local.properties': continue built = args.build / relative if not built.is_file() or digest(built) != digest(path): raise RuntimeError('Build source differs from current source: ' + relative.as_posix()) entries.append(dict(path=relative.as_posix(), sha256=digest(path))) apk = args.build / 'androidApp/build/outputs/apk/debug/androidApp-debug.apk' signer = args.sdk / 'build-tools/36.0.0/apksigner.bat' result = subprocess.run([str(signer), 'verify', '--print-certs', str(apk)], capture_output=True, text=True, encoding='utf-8', errors='replace') if result.returncode or 'CN=Android Debug' not in result.stdout: raise RuntimeError('APK must verify with the existing Android Debug certificate') certificate = re.search(r'certificate SHA-256 digest: ([0-9a-fA-F]+)', result.stdout)[1].lower() destination = root / '交付包' / f'{version}-{code}' destination.mkdir(parents=True, exist_ok=False) name = f'奇妙小屏幕-{version}-debug.apk' shutil.copyfile(apk, destination / name) versions = tomllib.loads((source / 'gradle/libs.versions.toml').read_text(encoding='utf-8'))['versions'] build_config = (args.build / 'androidApp/build/generated/source/buildConfig/debug/org/qimiaoscreen/controller/BuildConfig.java').read_text(encoding='utf-8') build_date = re.search(r'BUILD_DATE = "([^"]+)"', build_config)[1] manifest = dict(app_version=version, version_code=code, signing='debug', build_date=build_date, status='development-validation-see-test-report', apk_sha256=digest(apk), certificate_sha256=certificate, protocol_major=1, protocol_minor=3, minimum_android_api=26, target_android_api=37, build_tools='36.0.0', toolchain=versions, sources=entries) (destination / 'manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') (destination / 'SHA256SUMS').write_text(f'{digest(apk)} {name}\n', encoding='utf-8') (destination / 'README.md').write_text( f'# 奇妙小屏幕 {version} debug(versionCode {code})\n\n' '仅 debug 签名,可覆盖安装相同调试证书的旧包。Android 8.0 以上,真实验证机为 Android 13。\n\n' '本包为当前开发验证产物;验收范围和未验证项见 `../../../测试结果归档/实施状态.md`。' '不能将 APK 成功生成视为所有首版验收通过。设备需已部署移动控制功能,历史设备版本号本身不代表支持 BLE。\n\n' f'SHA-256:`{digest(apk)}`。源码摘要与签名证书摘要见 manifest.json。\n', encoding='utf-8') print(json.dumps(dict(delivery=destination.relative_to(root).as_posix(), apk_sha256=digest(apk), certificate_sha256=certificate), ensure_ascii=False)) if __name__ == '__main__': main()