Magisk/build.py

443 lines
15 KiB
Python
Raw Permalink Normal View History

2017-06-04 01:39:52 +08:00
#!/usr/bin/env python3
2017-06-03 20:19:01 +08:00
import sys
import os
import subprocess
2017-11-12 04:17:56 +08:00
if os.name == 'nt':
from colorama import init
init()
2017-06-03 20:19:01 +08:00
def error(str):
print('\n' + '\033[41m' + str + '\033[0m' + '\n')
sys.exit(1)
def header(str):
print('\n' + '\033[44m' + str + '\033[0m' + '\n')
# Environment checks
if not sys.version_info >= (3, 5):
error('Requires Python >= 3.5')
if 'ANDROID_HOME' not in os.environ:
error('Please add Android SDK path to ANDROID_HOME environment variable!')
try:
subprocess.run(['java', '-version'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except FileNotFoundError:
error('Please install Java and make sure \'java\' is available in PATH')
# If not Windows, we need gcc to compile
if os.name != 'nt':
try:
subprocess.run(['gcc', '-v'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except FileNotFoundError:
error('Please install C compiler and make sure \'gcc\' is available in PATH')
import argparse
import multiprocessing
import zipfile
import datetime
import errno
import shutil
import lzma
import base64
2017-11-15 05:25:19 +08:00
import tempfile
2017-06-03 20:19:01 +08:00
2017-12-04 15:16:41 +08:00
if 'ANDROID_NDK' in os.environ:
ndk_build = os.path.join(os.environ['ANDROID_NDK'], 'ndk-build')
else:
ndk_build = os.path.join(os.environ['ANDROID_HOME'], 'ndk-bundle', 'ndk-build')
def mv(source, target):
print('mv: {} -> {}'.format(source, target))
shutil.move(source, target)
def cp(source, target):
print('cp: {} -> {}'.format(source, target))
shutil.copyfile(source, target)
def rm(file):
2017-06-03 20:19:01 +08:00
try:
2017-12-04 18:05:07 +08:00
os.remove(file)
2017-06-03 20:19:01 +08:00
except OSError as e:
if e.errno != errno.ENOENT:
raise
def mkdir(path, mode=0o777):
try:
os.mkdir(path, mode)
except:
pass
def mkdir_p(path, mode=0o777):
os.makedirs(path, mode, exist_ok=True)
2017-06-03 20:19:01 +08:00
def zip_with_msg(zipfile, source, target):
if not os.path.exists(source):
error('{} does not exist! Try build \'binary\' and \'apk\' before zipping!'.format(source))
print('zip: {} -> {}'.format(source, target))
2017-06-03 20:19:01 +08:00
zipfile.write(source, target)
def build_all(args):
build_binary(args)
build_apk(args)
zip_main(args)
2017-06-04 00:03:36 +08:00
zip_uninstaller(args)
2017-11-22 16:04:24 +08:00
build_snet(args)
2017-06-03 20:19:01 +08:00
def build_binary(args):
header('* Building Magisk binaries')
# Force update logging.h timestamp to trigger recompilation
os.utime(os.path.join('core', 'jni', 'include', 'logging.h'))
2017-07-30 20:14:12 +08:00
debug_flag = '' if args.release else '-DMAGISK_DEBUG'
2017-11-23 23:55:33 +08:00
cflag = 'MAGISK_FLAGS=\"-DMAGISK_VERSION=\\\"{}\\\" -DMAGISK_VER_CODE={} {}\"'.format(args.versionString, args.versionCode, debug_flag)
# Prebuild
2017-12-04 15:16:41 +08:00
proc = subprocess.run('{} -C core PRECOMPILE=true {} -j{}'.format(ndk_build, cflag, multiprocessing.cpu_count()), shell=True)
if proc.returncode != 0:
error('Build Magisk binary failed!')
print('')
for arch in ['arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64']:
mkdir_p(os.path.join('out', arch))
with open(os.path.join('out', arch, 'dump.h'), 'w') as dump:
dump.write('#include "stdlib.h"\n')
2017-12-04 15:16:41 +08:00
mv(os.path.join('core', 'libs', arch, 'magisk'), os.path.join('out', arch, 'magisk'))
2017-11-10 00:54:54 +08:00
with open(os.path.join('out', arch, 'magisk'), 'rb') as bin:
dump.write('const uint8_t magisk_dump[] = "')
dump.write(''.join("\\x{:02X}".format(c) for c in lzma.compress(bin.read(), preset=9)))
dump.write('";\n')
print('')
2017-12-04 15:16:41 +08:00
proc = subprocess.run('{} -C core {} -j{}'.format(ndk_build, cflag, multiprocessing.cpu_count()), shell=True)
2017-06-03 20:19:01 +08:00
if proc.returncode != 0:
error('Build Magisk binary failed!')
print('')
for arch in ['arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64']:
2017-11-10 00:54:54 +08:00
for binary in ['magiskinit', 'magiskboot', 'b64xz', 'busybox']:
try:
2017-12-04 15:16:41 +08:00
mv(os.path.join('core', 'libs', arch, binary), os.path.join('out', arch, binary))
except:
pass
2017-06-03 20:19:01 +08:00
def build_apk(args):
header('* Building Magisk Manager')
for key in ['public.certificate.x509.pem', 'private.key.pk8']:
source = os.path.join('ziptools', key)
2017-12-04 15:16:41 +08:00
target = os.path.join('app', 'src', 'main', 'assets', key)
2017-10-07 22:48:16 +08:00
cp(source, target)
for script in ['magisk_uninstaller.sh', 'util_functions.sh']:
source = os.path.join('scripts', script)
2017-12-04 15:16:41 +08:00
target = os.path.join('app', 'src', 'main', 'assets', script)
2017-10-07 22:48:16 +08:00
cp(source, target)
2017-06-03 22:04:22 +08:00
if args.release:
2017-12-04 15:16:41 +08:00
if not os.path.exists('release_signature.jks'):
2017-06-03 20:19:01 +08:00
error('Please generate a java keystore and place it in \'release_signature.jks\'')
2017-10-07 22:48:16 +08:00
proc = subprocess.run('{} app:assembleRelease'.format(os.path.join('.', 'gradlew')), shell=True)
2017-06-03 20:19:01 +08:00
if proc.returncode != 0:
error('Build Magisk Manager failed!')
2017-06-16 04:07:45 +08:00
unsigned = os.path.join('app', 'build', 'outputs', 'apk', 'release', 'app-release-unsigned.apk')
aligned = os.path.join('app', 'build', 'outputs', 'apk', 'release', 'app-release-aligned.apk')
release = os.path.join('app', 'build', 'outputs', 'apk', 'release', 'app-release.apk')
2017-06-03 20:19:01 +08:00
# Find the latest build tools
build_tool = sorted(os.listdir(os.path.join(os.environ['ANDROID_HOME'], 'build-tools')))[-1]
rm(aligned)
rm(release)
2017-06-03 20:19:01 +08:00
proc = subprocess.run([
os.path.join(os.environ['ANDROID_HOME'], 'build-tools', build_tool, 'zipalign'),
2017-08-12 16:44:58 +08:00
'-v', '-p', '4', unsigned, aligned], stdout=subprocess.DEVNULL)
2017-06-03 20:19:01 +08:00
if proc.returncode != 0:
error('Zipalign Magisk Manager failed!')
2017-08-24 12:14:17 +08:00
# Find apksigner.jar
apksigner = ''
for root, dirs, files in os.walk(os.path.join(os.environ['ANDROID_HOME'], 'build-tools', build_tool)):
if 'apksigner.jar' in files:
apksigner = os.path.join(root, 'apksigner.jar')
break
if not apksigner:
error('Cannot find apksigner.jar in Android SDK build tools')
proc = subprocess.run('java -jar {} sign --ks {} --out {} {}'.format(
2017-12-04 15:16:41 +08:00
apksigner, 'release_signature.jks', release, aligned), shell=True)
2017-06-03 20:19:01 +08:00
if proc.returncode != 0:
error('Release sign Magisk Manager failed!')
rm(unsigned)
rm(aligned)
2017-12-04 15:16:41 +08:00
mkdir('out')
target = os.path.join('out', 'app-release.apk')
print('')
mv(release, target)
2017-06-03 22:04:22 +08:00
else:
2017-10-07 22:48:16 +08:00
proc = subprocess.run('{} app:assembleDebug'.format(os.path.join('.', 'gradlew')), shell=True)
2017-06-03 22:04:22 +08:00
if proc.returncode != 0:
error('Build Magisk Manager failed!')
source = os.path.join('app', 'build', 'outputs', 'apk', 'debug', 'app-debug.apk')
2017-12-04 15:16:41 +08:00
mkdir('out')
target = os.path.join('out', 'app-debug.apk')
print('')
mv(source, target)
2017-10-07 22:48:16 +08:00
def build_snet(args):
proc = subprocess.run('{} snet:assembleRelease'.format(os.path.join('.', 'gradlew')), shell=True)
if proc.returncode != 0:
error('Build snet extention failed!')
source = os.path.join('snet', 'build', 'outputs', 'apk', 'release', 'snet-release-unsigned.apk')
2017-12-04 15:16:41 +08:00
mkdir('out')
target = os.path.join('out', 'snet.apk')
2017-10-07 22:48:16 +08:00
print('')
mv(source, target)
2017-06-03 20:19:01 +08:00
def gen_update_binary():
update_bin = []
binary = os.path.join('out', 'armeabi-v7a', 'b64xz')
if not os.path.exists(binary):
error('Please build \'binary\' before zipping!')
with open(binary, 'rb') as b64xz:
2017-10-11 02:26:43 +08:00
update_bin.append('#! /sbin/sh\nEX_ARM=\'')
update_bin.append(''.join("\\x{:02X}".format(c) for c in b64xz.read()))
binary = os.path.join('out', 'x86', 'b64xz')
with open(binary, 'rb') as b64xz:
2017-10-11 02:26:43 +08:00
update_bin.append('\'\nEX_X86=\'')
update_bin.append(''.join("\\x{:02X}".format(c) for c in b64xz.read()))
binary = os.path.join('out', 'armeabi-v7a', 'busybox')
with open(binary, 'rb') as busybox:
2017-10-11 02:26:43 +08:00
update_bin.append('\'\nBB_ARM=')
update_bin.append(base64.b64encode(lzma.compress(busybox.read(), preset=9)).decode('ascii'))
binary = os.path.join('out', 'x86', 'busybox')
with open(binary, 'rb') as busybox:
update_bin.append('\nBB_X86=')
update_bin.append(base64.b64encode(lzma.compress(busybox.read(), preset=9)).decode('ascii'))
update_bin.append('\n')
with open(os.path.join('scripts', 'update_binary.sh'), 'r') as script:
update_bin.append(script.read())
return ''.join(update_bin)
2017-06-03 20:19:01 +08:00
def zip_main(args):
header('* Packing Flashable Zip')
2017-11-15 05:25:19 +08:00
unsigned = tempfile.mkstemp()[1]
with zipfile.ZipFile(unsigned, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf:
# META-INF
# update-binary
target = os.path.join('META-INF', 'com', 'google', 'android', 'update-binary')
print('zip: ' + target)
zipf.writestr(target, gen_update_binary())
# updater-script
source = os.path.join('scripts', 'flash_script.sh')
target = os.path.join('META-INF', 'com', 'google', 'android', 'updater-script')
zip_with_msg(zipf, source, target)
# Binaries
2017-06-03 20:19:01 +08:00
for lib_dir, zip_dir in [('arm64-v8a', 'arm64'), ('armeabi-v7a', 'arm'), ('x86', 'x86'), ('x86_64', 'x64')]:
2017-11-10 00:54:54 +08:00
for binary in ['magiskinit', 'magiskboot']:
source = os.path.join('out', lib_dir, binary)
2017-06-03 20:19:01 +08:00
target = os.path.join(zip_dir, binary)
zip_with_msg(zipf, source, target)
# APK
source = os.path.join('out', 'app-release.apk' if args.release else 'app-debug.apk')
2017-06-03 20:19:01 +08:00
target = os.path.join('common', 'magisk.apk')
zip_with_msg(zipf, source, target)
# Scripts
2017-06-19 00:15:44 +08:00
# boot_patch.sh
2017-06-03 20:19:01 +08:00
source = os.path.join('scripts', 'boot_patch.sh')
target = os.path.join('common', 'boot_patch.sh')
zip_with_msg(zipf, source, target)
2017-06-19 00:15:44 +08:00
# util_functions.sh
source = os.path.join('scripts', 'util_functions.sh')
2017-07-11 01:54:11 +08:00
with open(source, 'r') as script:
# Add version info util_functions.sh
util_func = script.read().replace(
2017-12-21 03:36:18 +08:00
'#MAGISK_VERSION_STUB', 'MAGISK_VER="{}"\nMAGISK_VER_CODE={}'.format(args.versionString, args.versionCode))
2017-07-11 01:54:11 +08:00
target = os.path.join('common', 'util_functions.sh')
print('zip: ' + source + ' -> ' + target)
zipf.writestr(target, util_func)
# addon.d.sh
source = os.path.join('scripts', 'addon.d.sh')
target = os.path.join('addon.d', '99-magisk.sh')
zip_with_msg(zipf, source, target)
2017-06-03 20:19:01 +08:00
# Prebuilts
for chromeos in ['futility', 'kernel_data_key.vbprivk', 'kernel.keyblock']:
source = os.path.join('chromeos', chromeos)
zip_with_msg(zipf, source, source)
# End of zipping
output = os.path.join('out', 'Magisk-v{}.zip'.format(args.versionString))
2017-11-15 05:25:19 +08:00
sign_adjust_zip(unsigned, output)
2017-06-03 20:19:01 +08:00
def zip_uninstaller(args):
header('* Packing Uninstaller Zip')
2017-11-15 05:25:19 +08:00
unsigned = tempfile.mkstemp()[1]
with zipfile.ZipFile(unsigned, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf:
# META-INF
# update-binary
target = os.path.join('META-INF', 'com', 'google', 'android', 'update-binary')
print('zip: ' + target)
zipf.writestr(target, gen_update_binary())
# updater-script
source = os.path.join('scripts', 'uninstaller_loader.sh')
target = os.path.join('META-INF', 'com', 'google', 'android', 'updater-script')
zip_with_msg(zipf, source, target)
# Binaries
2017-06-03 20:19:01 +08:00
for lib_dir, zip_dir in [('arm64-v8a', 'arm64'), ('armeabi-v7a', 'arm'), ('x86', 'x86'), ('x86_64', 'x64')]:
source = os.path.join('out', lib_dir, 'magiskboot')
2017-06-03 20:19:01 +08:00
target = os.path.join(zip_dir, 'magiskboot')
zip_with_msg(zipf, source, target)
source = os.path.join('scripts', 'magisk_uninstaller.sh')
2017-06-04 00:03:36 +08:00
target = 'magisk_uninstaller.sh'
zip_with_msg(zipf, source, target)
# Scripts
2017-07-11 01:54:11 +08:00
# util_functions.sh
2017-07-10 00:17:34 +08:00
source = os.path.join('scripts', 'util_functions.sh')
2017-07-11 01:54:11 +08:00
with open(source, 'r') as script:
# Remove the stub
target = os.path.join('util_functions.sh')
print('zip: ' + source + ' -> ' + target)
2017-12-21 03:36:18 +08:00
zipf.writestr(target, script.read())
2017-07-10 00:17:34 +08:00
# Prebuilts
for chromeos in ['futility', 'kernel_data_key.vbprivk', 'kernel.keyblock']:
source = os.path.join('chromeos', chromeos)
zip_with_msg(zipf, source, source)
# End of zipping
output = os.path.join('out', 'Magisk-uninstaller-{}.zip'.format(datetime.datetime.now().strftime('%Y%m%d')))
2017-11-15 05:25:19 +08:00
sign_adjust_zip(unsigned, output)
2017-06-03 20:19:01 +08:00
def sign_adjust_zip(unsigned, output):
2017-12-04 15:16:41 +08:00
signer_name = 'zipsigner-1.1.jar'
jarsigner = os.path.join('crypto', 'build', 'libs', signer_name)
if os.name != 'nt' and not os.path.exists(os.path.join('ziptools', 'zipadjust')):
header('* Building zipadjust')
# Compile zipadjust
proc = subprocess.run('gcc -o ziptools/zipadjust ziptools/zipadjust_src/*.c -lz', shell=True)
if proc.returncode != 0:
error('Build zipadjust failed!')
if not os.path.exists(jarsigner):
header('* Building ' + signer_name)
proc = subprocess.run('{} crypto:shadowJar'.format(os.path.join('.', 'gradlew')), shell=True)
if proc.returncode != 0:
error('Build {} failed!'.format(signer_name))
header('* Signing / Adjusting Zip')
publicKey = os.path.join('ziptools', 'public.certificate.x509.pem')
privateKey = os.path.join('ziptools', 'private.key.pk8')
2017-11-15 05:25:19 +08:00
signed = tempfile.mkstemp()[1]
# Unsigned->signed
proc = subprocess.run(['java', '-jar', jarsigner,
2017-11-15 05:25:19 +08:00
publicKey, privateKey, unsigned, signed])
if proc.returncode != 0:
error('First sign flashable zip failed!')
2017-11-15 05:25:19 +08:00
adjusted = tempfile.mkstemp()[1]
# Adjust zip
2017-11-15 05:25:19 +08:00
proc = subprocess.run([os.path.join('ziptools', 'zipadjust'), signed, adjusted])
if proc.returncode != 0:
error('Adjust flashable zip failed!')
# Adjusted -> output
proc = subprocess.run(['java', '-jar', jarsigner,
2017-11-15 05:25:19 +08:00
"-m", publicKey, privateKey, adjusted, output])
if proc.returncode != 0:
error('Second sign flashable zip failed!')
# Cleanup
rm(unsigned)
2017-11-15 05:25:19 +08:00
rm(signed)
rm(adjusted)
2017-06-03 20:19:01 +08:00
def cleanup(args):
if len(args.target) == 0:
2017-10-07 22:48:16 +08:00
args.target = ['binary', 'java', 'zip']
2017-06-03 20:19:01 +08:00
if 'binary' in args.target:
2017-10-07 22:48:16 +08:00
header('* Cleaning binaries')
subprocess.run(ndk_build + ' -C core PRECOMPILE=true clean', shell=True)
subprocess.run(ndk_build + ' -C core clean', shell=True)
for arch in ['arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64']:
shutil.rmtree(os.path.join('out', arch), ignore_errors=True)
2017-06-03 20:19:01 +08:00
2017-10-07 22:48:16 +08:00
if 'java' in args.target:
header('* Cleaning java')
2017-12-19 15:51:01 +08:00
subprocess.run('{} app:clean snet:clean crypto:clean'.format(os.path.join('.', 'gradlew')), shell=True)
for f in os.listdir('out'):
if '.apk' in f:
rm(os.path.join('out', f))
2017-06-03 20:19:01 +08:00
if 'zip' in args.target:
2017-10-07 22:48:16 +08:00
header('* Cleaning zip files')
for f in os.listdir('out'):
2017-06-03 20:19:01 +08:00
if '.zip' in f:
rm(os.path.join('out', f))
2017-06-03 20:19:01 +08:00
parser = argparse.ArgumentParser(description='Magisk build script')
2017-06-03 22:04:22 +08:00
parser.add_argument('--release', action='store_true', help='compile Magisk for release')
2017-06-03 20:19:01 +08:00
subparsers = parser.add_subparsers(title='actions')
2017-06-04 00:03:36 +08:00
all_parser = subparsers.add_parser('all', help='build everything and create flashable zip with uninstaller')
2017-06-03 20:19:01 +08:00
all_parser.add_argument('versionString')
all_parser.add_argument('versionCode', type=int)
all_parser.set_defaults(func=build_all)
binary_parser = subparsers.add_parser('binary', help='build Magisk binaries')
binary_parser.add_argument('versionString')
binary_parser.add_argument('versionCode', type=int)
binary_parser.set_defaults(func=build_binary)
apk_parser = subparsers.add_parser('apk', help='build Magisk Manager APK')
apk_parser.set_defaults(func=build_apk)
2017-10-07 22:48:16 +08:00
snet_parser = subparsers.add_parser('snet', help='build snet extention for Magisk Manager')
snet_parser.set_defaults(func=build_snet)
2017-06-03 20:19:01 +08:00
zip_parser = subparsers.add_parser('zip', help='zip and sign Magisk into a flashable zip')
zip_parser.add_argument('versionString')
2017-07-11 01:54:11 +08:00
zip_parser.add_argument('versionCode', type=int)
2017-06-03 20:19:01 +08:00
zip_parser.set_defaults(func=zip_main)
uninstaller_parser = subparsers.add_parser('uninstaller', help='create flashable uninstaller')
uninstaller_parser.set_defaults(func=zip_uninstaller)
2017-10-07 22:48:16 +08:00
clean_parser = subparsers.add_parser('clean', help='clean [target...] targets: binary java zip')
2017-06-03 20:19:01 +08:00
clean_parser.add_argument('target', nargs='*')
clean_parser.set_defaults(func=cleanup)
if len(sys.argv) == 1:
2017-12-04 18:05:07 +08:00
parser.print_help()
sys.exit(1)
2017-06-03 20:19:01 +08:00
args = parser.parse_args()
args.func(args)