66 lines
3.8 KiB
Python
66 lines
3.8 KiB
Python
"""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=2, 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()
|