"""Build Windows/Linux usando JDK e Android SDK existentes; sem Gradle.""" import argparse import os from pathlib import Path import shutil import subprocess import tempfile import zipfile HERE = Path(__file__).resolve().parent DEFAULT_TOOLS = os.environ.get('CARINHA_TOOLS', 'C:/Users/JullioPC/Documents/Open/orange-pi-zero3/android/tools') def main(): parser = argparse.ArgumentParser() parser.add_argument('--tools', default=DEFAULT_TOOLS) args = parser.parse_args() tools = Path(args.tools) suffix = '.exe' if os.name == 'nt' else '' java = tools / 'jdk/bin' / ('java' + suffix) javac = tools / 'jdk/bin' / ('javac' + suffix) bt = tools / 'android-sdk/build-tools/34.0.0' android = tools / 'android-sdk/platforms/android-34/android.jar' for path in (java, javac, android, bt / ('aapt2' + suffix), HERE / 'keystore.jks'): if not path.exists(): raise SystemExit('Arquivo necessário não encontrado: ' + str(path)) work = HERE.parents[2] / 'work' work.mkdir(parents=True, exist_ok=True) def run(*command): subprocess.run([str(x) for x in command], check=True) for name in ('carinha.html', 'camera.html'): shutil.copy2(HERE.parent / name, HERE / 'app/assets' / name) with tempfile.TemporaryDirectory(prefix='carinha-build-', dir=work) as folder: tmp = Path(folder) for name in ('compiled', 'classes', 'gen'): (tmp / name).mkdir() print('Recursos e manifesto', flush=True) run(bt / ('aapt2' + suffix), 'compile', '--dir', HERE / 'app/res', '-o', tmp / 'compiled/res.zip') run(bt / ('aapt2' + suffix), 'link', '-o', tmp / 'base.apk', '-I', android, '--manifest', HERE / 'app/AndroidManifest.xml', '-A', HERE / 'app/assets', '--java', tmp / 'gen', '--min-sdk-version', '21', '--target-sdk-version', '34', tmp / 'compiled/res.zip') sources = list((HERE / 'app/java').rglob('*.java')) + list((tmp / 'gen').rglob('*.java')) print('Java e DEX', flush=True) run(javac, '--release', '8', '-nowarn', '-classpath', android, '-d', tmp / 'classes', *sources) classes = list((tmp / 'classes').rglob('*.class')) run(java, '-cp', bt / 'lib/d8.jar', 'com.android.tools.r8.D8', '--lib', android, '--min-api', '21', '--output', tmp, *classes) with zipfile.ZipFile(tmp / 'base.apk', 'a', zipfile.ZIP_DEFLATED) as z: z.write(tmp / 'classes.dex', 'classes.dex') for native in (HERE / 'app/native/arm64-v8a').glob('*.so'): z.write(native, 'lib/arm64-v8a/' + native.name) run(bt / ('zipalign' + suffix), '-f', '-p', '4', tmp / 'base.apk', tmp / 'aligned.apk') print('Assinatura persistente', flush=True) run(java, '-jar', bt / 'lib/apksigner.jar', 'sign', '--ks', HERE / 'keystore.jks', '--ks-pass', 'pass:android', '--key-pass', 'pass:android', '--ks-key-alias', 'carinha', '--out', HERE / 'carinha-rosto.apk', tmp / 'aligned.apk') run(java, '-jar', bt / 'lib/apksigner.jar', 'verify', HERE / 'carinha-rosto.apk') with zipfile.ZipFile(HERE / 'carinha-rosto.apk') as z: required = {'AndroidManifest.xml', 'classes.dex', 'resources.arsc', 'assets/carinha.html', 'assets/camera.html', 'assets/transmissao.html', 'assets/qualidade-auto.js'} assert required.issubset(z.namelist()), 'APK incompleto' print('APK pronto: ' + str(HERE / 'carinha-rosto.apk')) if __name__ == '__main__': main()