Let build.py setup NDK

This commit is contained in:
topjohnwu 2020-04-03 03:33:39 -07:00
parent 2f1f68f12f
commit 67d746a62c
22 changed files with 193 additions and 109 deletions

View File

@ -47,20 +47,21 @@ For installation issues, upload both boot image and install logs.<br>
For Magisk issues, upload boot logcat or dmesg.<br> For Magisk issues, upload boot logcat or dmesg.<br>
For Magisk Manager crashes, record and upload the logcat when the crash occurs. For Magisk Manager crashes, record and upload the logcat when the crash occurs.
## Building Magisk ## Building and Development
- Magisk builds on any OS Android Studio supports. Install Android Studio and do the initial setups.
- Clone sources: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git` - Clone sources: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
- Magisk builds on any OS Android Studio supports. Install Android Studio and import the project. - Install Python 3.6+. For Windows only, install Colorama with `pip install colorama` in admin shell.
- Python 3.6+. For Windows only, install Colorama with `pip install colorama` in admin shell. - Configure to use the JDK bundled in Android Studio:
- Use the JDK bundled in Android Studio:
- macOS: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home"` - macOS: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home"`
- Linux: `export PATH="/path/to/androidstudio/jre/bin:$PATH"` - Linux: `export PATH="/path/to/androidstudio/jre/bin:$PATH"`
- Windows: Add `C:\Path\To\Android Studio\jre\bin` to environment variable `PATH` - Windows: Add `C:\Path\To\Android Studio\jre\bin` to environment variable `PATH`
- Set environment variable `ANDROID_HOME` to the Android SDK folder - Set environment variable `ANDROID_HOME` to the Android SDK folder (can be found in Android Studio settings)
- Download / clone [FrankeNDK](https://github.com/topjohnwu/FrankeNDK) and set environment variable `ANDROID_NDK_HOME` to the folder - Run `./build.py ndk` to let the script download and install NDK for you
- Set configurations in `config.prop`. A sample file `config.prop.sample` is provided. - Set configurations in `config.prop`. A sample `config.prop.sample` is provided.
- Run `build.py` to see help messages. For each supported actions, use `-h` to access help (e.g. `./build.py all -h`) - To start building, run `build.py` to see your options. For each action, use `-h` to access help (e.g. `./build.py all -h`)
- By default, the script builds everything in debug mode. If you want to build Magisk Manager in release mode (with the `-r, --release` flag), you need a Java Keystore (only `JKS` format is supported) to sign APKs and zips. For more information, check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key). - To start development, open the project in Android Studio. Both app (Kotlin/Java) and native (C++/C) source code can be properly developed using the IDE, but *always* use `build.py` for building.
- `build.py` builds in debug mode by default. If you want release builds (with `-r, --release`), you need a Java Keystore to sign APKs and zips. For more information, check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key).
## Translation Contributions ## Translation Contributions

277
build.py
View File

@ -31,10 +31,10 @@ if 'ANDROID_HOME' not in os.environ:
error('Please add Android SDK path to ANDROID_HOME environment variable!') error('Please add Android SDK path to ANDROID_HOME environment variable!')
try: try:
subprocess.run(['java', '-version'], subprocess.run(['javac', '-version'],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except FileNotFoundError: except FileNotFoundError:
error('Please install JDK and make sure \'java\' is available in PATH') error('Please install JDK and make sure \'javac\' is available in PATH')
import argparse import argparse
import multiprocessing import multiprocessing
@ -44,21 +44,26 @@ import errno
import shutil import shutil
import lzma import lzma
import tempfile import tempfile
import platform
# Constants import urllib.request
if 'ANDROID_NDK_HOME' in os.environ: import os.path as op
ndk_build = os.path.join(os.environ['ANDROID_NDK_HOME'], 'ndk-build') from distutils.dir_util import copy_tree
else:
ndk_build = os.path.join(
os.environ['ANDROID_HOME'], 'ndk-bundle', 'ndk-build')
cpu_count = multiprocessing.cpu_count() cpu_count = multiprocessing.cpu_count()
gradlew = os.path.join('.', 'gradlew' + ('.bat' if is_windows else ''))
archs = ['armeabi-v7a', 'x86'] archs = ['armeabi-v7a', 'x86']
arch64 = ['arm64-v8a', 'x86_64'] arch64 = ['arm64-v8a', 'x86_64']
support_targets = ['magisk', 'magiskinit', 'magiskboot', 'magiskpolicy', 'resetprop', 'busybox', 'test'] support_targets = ['magisk', 'magiskinit', 'magiskboot', 'magiskpolicy', 'resetprop', 'busybox', 'test']
default_targets = ['magisk', 'magiskinit', 'magiskboot', 'busybox'] default_targets = ['magisk', 'magiskinit', 'magiskboot', 'busybox']
build_tools = os.path.join(os.environ['ANDROID_HOME'], 'build-tools', '29.0.3')
ndk_ver = '21'
ndk_ver_full = '21.0.6113669'
build_tools_ver = '29.0.3'
ndk_root = op.join(os.environ['ANDROID_HOME'], 'ndk')
ndk_path = op.join(ndk_root, 'magisk')
ndk_build = op.join(ndk_path, 'ndk-build')
build_tools = op.join(os.environ['ANDROID_HOME'], 'build-tools', build_tools_ver)
gradlew = op.join('.', 'gradlew' + ('.bat' if is_windows else ''))
# Global vars # Global vars
config = {} config = {}
@ -67,7 +72,7 @@ STDOUT = None
def mv(source, target): def mv(source, target):
try: try:
shutil.move(source, target) shutil.move(source, target)
vprint(f'mv: {source} -> {target}') vprint(f'mv {source} -> {target}')
except: except:
pass pass
@ -75,7 +80,7 @@ def mv(source, target):
def cp(source, target): def cp(source, target):
try: try:
shutil.copyfile(source, target) shutil.copyfile(source, target)
vprint(f'cp: {source} -> {target}') vprint(f'cp {source} -> {target}')
except: except:
pass pass
@ -83,20 +88,25 @@ def cp(source, target):
def rm(file): def rm(file):
try: try:
os.remove(file) os.remove(file)
vprint(f'rm: {file}') vprint(f'rm {file}')
except OSError as e: except OSError as e:
if e.errno != errno.ENOENT: if e.errno != errno.ENOENT:
raise raise
def mkdir(path, mode=0o777): def rm_rf(path):
vprint(f'rm -rf {path}')
shutil.rmtree(path, ignore_errors=True)
def mkdir(path, mode=0o755):
try: try:
os.mkdir(path, mode) os.mkdir(path, mode)
except: except:
pass pass
def mkdir_p(path, mode=0o777): def mkdir_p(path, mode=0o755):
os.makedirs(path, mode, exist_ok=True) os.makedirs(path, mode, exist_ok=True)
@ -112,28 +122,35 @@ def xz(data):
return lzma.compress(data, preset=9, check=lzma.CHECK_NONE) return lzma.compress(data, preset=9, check=lzma.CHECK_NONE)
def load_config(args): def parse_props(file):
# Some default values props = {}
config['outdir'] = 'out' with open(file, 'r') as f:
config['prettyName'] = 'false'
config['keyStore'] = 'release-key.jks'
# Load prop file
if not os.path.exists(args.config):
error(f'Please make sure {args.config} existed')
with open(args.config, 'r') as f:
for line in [l.strip(' \t\r\n') for l in f]: for line in [l.strip(' \t\r\n') for l in f]:
if line.startswith('#') or len(line) == 0: if line.startswith('#') or len(line) == 0:
continue continue
prop = line.split('=') prop = line.split('=')
if len(prop) != 2: if len(prop) != 2:
continue continue
config[prop[0].strip(' \t\r\n')] = prop[1].strip(' \t\r\n') props[prop[0].strip(' \t\r\n')] = prop[1].strip(' \t\r\n')
return props
def load_config(args):
# Load prop file
if not op.exists(args.config):
error(f'Please make sure {args.config} exists')
# Some default values
config['outdir'] = 'out'
config['prettyName'] = 'false'
config['keyStore'] = 'release-key.jks'
config.update(parse_props(args.config))
# Sanitize configs
config['prettyName'] = config['prettyName'].lower() == 'true' config['prettyName'] = config['prettyName'].lower() == 'true'
# Sanitize configs
if 'version' not in config or 'versionCode' not in config: if 'version' not in config or 'versionCode' not in config:
error('Config error: "version" and "versionCode" is required') error('Config error: "version" and "versionCode" is required')
@ -142,7 +159,7 @@ def load_config(args):
except ValueError: except ValueError:
error('Config error: "versionCode" is required to be an integer') error('Config error: "versionCode" is required to be an integer')
if args.release and not os.path.exists(config['keyStore']): if args.release and not op.exists(config['keyStore']):
error(f'Config error: assign "keyStore" to a java keystore') error(f'Config error: assign "keyStore" to a java keystore')
mkdir_p(config['outdir']) mkdir_p(config['outdir'])
@ -151,7 +168,7 @@ def load_config(args):
def zip_with_msg(zip_file, source, target): def zip_with_msg(zip_file, source, target):
if not os.path.exists(source): if not op.exists(source):
error(f'{source} does not exist! Try build \'binary\' and \'apk\' before zipping!') error(f'{source} does not exist! Try build \'binary\' and \'apk\' before zipping!')
zip_file.write(source, target) zip_file.write(source, target)
vprint(f'zip: {source} -> {target}') vprint(f'zip: {source} -> {target}')
@ -159,23 +176,23 @@ def zip_with_msg(zip_file, source, target):
def collect_binary(): def collect_binary():
for arch in archs + arch64: for arch in archs + arch64:
mkdir_p(os.path.join('native', 'out', arch)) mkdir_p(op.join('native', 'out', arch))
for bin in support_targets + ['magiskinit64']: for bin in support_targets + ['magiskinit64']:
source = os.path.join('native', 'libs', arch, bin) source = op.join('native', 'libs', arch, bin)
target = os.path.join('native', 'out', arch, bin) target = op.join('native', 'out', arch, bin)
mv(source, target) mv(source, target)
def clean_elf(): def clean_elf():
if is_windows: if is_windows:
elf_cleaner = os.path.join('tools', 'elf-cleaner.exe') elf_cleaner = op.join('tools', 'elf-cleaner.exe')
else: else:
elf_cleaner = os.path.join('native', 'out', 'elf-cleaner') elf_cleaner = op.join('native', 'out', 'elf-cleaner')
if not os.path.exists(elf_cleaner): if not op.exists(elf_cleaner):
execv(['g++', '-std=c++11', 'tools/termux-elf-cleaner/termux-elf-cleaner.cpp', execv(['g++', '-std=c++11', 'tools/termux-elf-cleaner/termux-elf-cleaner.cpp',
'-o', elf_cleaner]) '-o', elf_cleaner])
args = [elf_cleaner] args = [elf_cleaner]
args.extend(os.path.join('native', 'out', arch, 'magisk') for arch in archs + arch64) args.extend(op.join('native', 'out', arch, 'magisk') for arch in archs + arch64)
execv(args) execv(args)
@ -185,9 +202,9 @@ def sign_zip(unsigned, output, release):
return return
signer_name = 'zipsigner-3.0.jar' signer_name = 'zipsigner-3.0.jar'
zipsigner = os.path.join('signing', 'build', 'libs', signer_name) zipsigner = op.join('signing', 'build', 'libs', signer_name)
if not os.path.exists(zipsigner): if not op.exists(zipsigner):
header('* Building ' + signer_name) header('* Building ' + signer_name)
proc = execv([gradlew, 'signing:shadowJar']) proc = execv([gradlew, 'signing:shadowJar'])
if proc.returncode != 0: if proc.returncode != 0:
@ -215,13 +232,13 @@ def binary_dump(src, out, var_name):
def gen_update_binary(): def gen_update_binary():
bs = 1024 bs = 1024
update_bin = bytearray(bs) update_bin = bytearray(bs)
file = os.path.join('native', 'out', 'x86', 'busybox') file = op.join('native', 'out', 'x86', 'busybox')
with open(file, 'rb') as f: with open(file, 'rb') as f:
x86_bb = f.read() x86_bb = f.read()
file = os.path.join('native', 'out', 'armeabi-v7a', 'busybox') file = op.join('native', 'out', 'armeabi-v7a', 'busybox')
with open(file, 'rb') as f: with open(file, 'rb') as f:
arm_bb = f.read() arm_bb = f.read()
file = os.path.join('scripts', 'update_binary.sh') file = op.join('scripts', 'update_binary.sh')
with open(file, 'rb') as f: with open(file, 'rb') as f:
script = f.read() script = f.read()
# Align x86 busybox to bs # Align x86 busybox to bs
@ -244,28 +261,33 @@ def run_ndk_build(flags):
def dump_bin_headers(): def dump_bin_headers():
for arch in archs: for arch in archs:
bin_file = os.path.join('native', 'out', arch, 'magisk') bin_file = op.join('native', 'out', arch, 'magisk')
if not os.path.exists(bin_file): if not op.exists(bin_file):
error('Build "magisk" before building "magiskinit"') error('Build "magisk" before building "magiskinit"')
with open(os.path.join('native', 'out', arch, 'binaries_arch.h'), 'w') as out: with open(op.join('native', 'out', arch, 'binaries_arch.h'), 'w') as out:
with open(bin_file, 'rb') as src: with open(bin_file, 'rb') as src:
binary_dump(src, out, 'magisk_xz') binary_dump(src, out, 'magisk_xz')
for arch, arch32 in list(zip(arch64, archs)): for arch, arch32 in list(zip(arch64, archs)):
bin_file = os.path.join('native', 'out', arch, 'magisk') bin_file = op.join('native', 'out', arch, 'magisk')
with open(os.path.join('native', 'out', arch32, 'binaries_arch64.h'), 'w') as out: with open(op.join('native', 'out', arch32, 'binaries_arch64.h'), 'w') as out:
with open(bin_file, 'rb') as src: with open(bin_file, 'rb') as src:
binary_dump(src, out, 'magisk_xz') binary_dump(src, out, 'magisk_xz')
stub = os.path.join(config['outdir'], 'stub-release.apk') stub = op.join(config['outdir'], 'stub-release.apk')
if not os.path.exists(stub): if not op.exists(stub):
stub = os.path.join(config['outdir'], 'stub-debug.apk') stub = op.join(config['outdir'], 'stub-debug.apk')
if not os.path.exists(stub): if not op.exists(stub):
error('Build stub APK before building "magiskinit"') error('Build stub APK before building "magiskinit"')
with open(os.path.join('native', 'out', 'binaries.h'), 'w') as out: with open(op.join('native', 'out', 'binaries.h'), 'w') as out:
with open(stub, 'rb') as src: with open(stub, 'rb') as src:
binary_dump(src, out, 'manager_xz') binary_dump(src, out, 'manager_xz')
def build_binary(args): def build_binary(args):
# Verify NDK install
props = parse_props(op.join(ndk_path, 'source.properties'))
if 'Pkg.Revision.orig' not in props or props['Pkg.Revision.orig'] != ndk_ver_full:
error('Incorrect NDK. Please setup NDK with "build.py ndk"')
if args.target: if args.target:
args.target = set(args.target) & set(support_targets) args.target = set(args.target) & set(support_targets)
if not args.target: if not args.target:
@ -275,7 +297,7 @@ def build_binary(args):
header('* Building binaries: ' + ' '.join(args.target)) header('* Building binaries: ' + ' '.join(args.target))
os.utime(os.path.join('native', 'jni', 'include', 'flags.h')) os.utime(op.join('native', 'jni', 'include', 'flags.h'))
# Basic flags # Basic flags
global base_flags global base_flags
@ -312,20 +334,20 @@ def build_apk(args, module):
build_type = 'Release' if args.release else 'Debug' build_type = 'Release' if args.release else 'Debug'
proc = execv([gradlew, f'{module}:assemble{build_type}', proc = execv([gradlew, f'{module}:assemble{build_type}',
'-PconfigPath=' + os.path.abspath(args.config)]) '-PconfigPath=' + op.abspath(args.config)])
if proc.returncode != 0: if proc.returncode != 0:
error(f'Build {module} failed!') error(f'Build {module} failed!')
build_type = build_type.lower() build_type = build_type.lower()
apk = f'{module}-{build_type}.apk' apk = f'{module}-{build_type}.apk'
source = os.path.join(module, 'build', 'outputs', 'apk', build_type, apk) source = op.join(module, 'build', 'outputs', 'apk', build_type, apk)
target = os.path.join(config['outdir'], apk) target = op.join(config['outdir'], apk)
if args.release: if args.release:
zipalign = os.path.join(build_tools, 'zipalign' + ('.exe' if is_windows else '')) zipalign = op.join(build_tools, 'zipalign' + ('.exe' if is_windows else ''))
aapt2 = os.path.join(build_tools, 'aapt2' + ('.exe' if is_windows else '')) aapt2 = op.join(build_tools, 'aapt2' + ('.exe' if is_windows else ''))
apksigner = os.path.join(build_tools, 'apksigner' + ('.bat' if is_windows else '')) apksigner = op.join(build_tools, 'apksigner' + ('.bat' if is_windows else ''))
try: try:
with tempfile.NamedTemporaryFile(delete=False) as f: with tempfile.NamedTemporaryFile(delete=False) as f:
tmp = f.name tmp = f.name
@ -361,9 +383,9 @@ def build_apk(args, module):
def build_app(args): def build_app(args):
header('* Building Magisk Manager') header('* Building Magisk Manager')
source = os.path.join('scripts', 'util_functions.sh') source = op.join('scripts', 'util_functions.sh')
target = os.path.join('app', 'src', 'main', target = op.join('app', 'src', 'main',
'res', 'raw', 'util_functions.sh') 'res', 'raw', 'util_functions.sh')
cp(source, target) cp(source, target)
build_apk(args, 'app') build_apk(args, 'app')
@ -378,9 +400,9 @@ def build_snet(args):
proc = execv([gradlew, 'snet:assembleRelease']) proc = execv([gradlew, 'snet:assembleRelease'])
if proc.returncode != 0: if proc.returncode != 0:
error('Build snet extention failed!') error('Build snet extention failed!')
source = os.path.join('snet', 'build', 'outputs', 'apk', source = op.join('snet', 'build', 'outputs', 'apk',
'release', 'snet-release-unsigned.apk') 'release', 'snet-release-unsigned.apk')
target = os.path.join(config['outdir'], 'snet.jar') target = op.join(config['outdir'], 'snet.jar')
# Extract classes.dex # Extract classes.dex
with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zout: with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zout:
with zipfile.ZipFile(source) as zin: with zipfile.ZipFile(source) as zin:
@ -397,59 +419,59 @@ def zip_main(args):
with zipfile.ZipFile(unsigned, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf: with zipfile.ZipFile(unsigned, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf:
# update-binary # update-binary
target = os.path.join('META-INF', 'com', 'google', target = op.join('META-INF', 'com', 'google',
'android', 'update-binary') 'android', 'update-binary')
vprint('zip: ' + target) vprint('zip: ' + target)
zipf.writestr(target, gen_update_binary()) zipf.writestr(target, gen_update_binary())
# updater-script # updater-script
source = os.path.join('scripts', 'flash_script.sh') source = op.join('scripts', 'flash_script.sh')
target = os.path.join('META-INF', 'com', 'google', target = op.join('META-INF', 'com', 'google',
'android', 'updater-script') 'android', 'updater-script')
zip_with_msg(zipf, source, target) zip_with_msg(zipf, source, target)
# Binaries # Binaries
for lib_dir, zip_dir in [('armeabi-v7a', 'arm'), ('x86', 'x86')]: for lib_dir, zip_dir in [('armeabi-v7a', 'arm'), ('x86', 'x86')]:
for binary in ['magiskinit', 'magiskinit64', 'magiskboot']: for binary in ['magiskinit', 'magiskinit64', 'magiskboot']:
source = os.path.join('native', 'out', lib_dir, binary) source = op.join('native', 'out', lib_dir, binary)
target = os.path.join(zip_dir, binary) target = op.join(zip_dir, binary)
zip_with_msg(zipf, source, target) zip_with_msg(zipf, source, target)
# APK # APK
source = os.path.join( source = op.join(
config['outdir'], 'app-release.apk' if args.release else 'app-debug.apk') config['outdir'], 'app-release.apk' if args.release else 'app-debug.apk')
target = os.path.join('common', 'magisk.apk') target = op.join('common', 'magisk.apk')
zip_with_msg(zipf, source, target) zip_with_msg(zipf, source, target)
# boot_patch.sh # boot_patch.sh
source = os.path.join('scripts', 'boot_patch.sh') source = op.join('scripts', 'boot_patch.sh')
target = os.path.join('common', 'boot_patch.sh') target = op.join('common', 'boot_patch.sh')
zip_with_msg(zipf, source, target) zip_with_msg(zipf, source, target)
# util_functions.sh # util_functions.sh
source = os.path.join('scripts', 'util_functions.sh') source = op.join('scripts', 'util_functions.sh')
with open(source, 'r') as script: with open(source, 'r') as script:
# Add version info util_functions.sh # Add version info util_functions.sh
util_func = script.read().replace( util_func = script.read().replace(
'#MAGISK_VERSION_STUB', '#MAGISK_VERSION_STUB',
f'MAGISK_VER="{config["version"]}"\nMAGISK_VER_CODE={config["versionCode"]}') f'MAGISK_VER="{config["version"]}"\nMAGISK_VER_CODE={config["versionCode"]}')
target = os.path.join('common', 'util_functions.sh') target = op.join('common', 'util_functions.sh')
vprint(f'zip: {source} -> {target}') vprint(f'zip: {source} -> {target}')
zipf.writestr(target, util_func) zipf.writestr(target, util_func)
# addon.d.sh # addon.d.sh
source = os.path.join('scripts', 'addon.d.sh') source = op.join('scripts', 'addon.d.sh')
target = os.path.join('common', 'addon.d.sh') target = op.join('common', 'addon.d.sh')
zip_with_msg(zipf, source, target) zip_with_msg(zipf, source, target)
# chromeos # chromeos
for tool in ['futility', 'kernel_data_key.vbprivk', 'kernel.keyblock']: for tool in ['futility', 'kernel_data_key.vbprivk', 'kernel.keyblock']:
source = os.path.join('tools', tool) source = op.join('tools', tool)
target = os.path.join('chromeos', tool) target = op.join('chromeos', tool)
zip_with_msg(zipf, source, target) zip_with_msg(zipf, source, target)
# End of zipping # End of zipping
output = os.path.join(config['outdir'], f'Magisk-v{config["version"]}.zip' if config['prettyName'] else output = op.join(config['outdir'], f'Magisk-v{config["version"]}.zip' if config['prettyName'] else
'magisk-release.zip' if args.release else 'magisk-debug.zip') 'magisk-release.zip' if args.release else 'magisk-debug.zip')
sign_zip(unsigned, output, args.release) sign_zip(unsigned, output, args.release)
rm(unsigned) rm(unsigned)
header('Output: ' + output) header('Output: ' + output)
@ -463,40 +485,40 @@ def zip_uninstaller(args):
with zipfile.ZipFile(unsigned, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf: with zipfile.ZipFile(unsigned, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf:
# update-binary # update-binary
target = os.path.join('META-INF', 'com', 'google', target = op.join('META-INF', 'com', 'google',
'android', 'update-binary') 'android', 'update-binary')
vprint('zip: ' + target) vprint('zip: ' + target)
zipf.writestr(target, gen_update_binary()) zipf.writestr(target, gen_update_binary())
# updater-script # updater-script
source = os.path.join('scripts', 'magisk_uninstaller.sh') source = op.join('scripts', 'magisk_uninstaller.sh')
target = os.path.join('META-INF', 'com', 'google', target = op.join('META-INF', 'com', 'google',
'android', 'updater-script') 'android', 'updater-script')
zip_with_msg(zipf, source, target) zip_with_msg(zipf, source, target)
# Binaries # Binaries
for lib_dir, zip_dir in [('armeabi-v7a', 'arm'), ('x86', 'x86')]: for lib_dir, zip_dir in [('armeabi-v7a', 'arm'), ('x86', 'x86')]:
source = os.path.join('native', 'out', lib_dir, 'magiskboot') source = op.join('native', 'out', lib_dir, 'magiskboot')
target = os.path.join(zip_dir, 'magiskboot') target = op.join(zip_dir, 'magiskboot')
zip_with_msg(zipf, source, target) zip_with_msg(zipf, source, target)
# util_functions.sh # util_functions.sh
source = os.path.join('scripts', 'util_functions.sh') source = op.join('scripts', 'util_functions.sh')
with open(source, 'r') as script: with open(source, 'r') as script:
target = os.path.join('util_functions.sh') target = op.join('util_functions.sh')
vprint(f'zip: {source} -> {target}') vprint(f'zip: {source} -> {target}')
zipf.writestr(target, script.read()) zipf.writestr(target, script.read())
# chromeos # chromeos
for tool in ['futility', 'kernel_data_key.vbprivk', 'kernel.keyblock']: for tool in ['futility', 'kernel_data_key.vbprivk', 'kernel.keyblock']:
source = os.path.join('tools', tool) source = op.join('tools', tool)
target = os.path.join('chromeos', tool) target = op.join('chromeos', tool)
zip_with_msg(zipf, source, target) zip_with_msg(zipf, source, target)
# End of zipping # End of zipping
datestr = datetime.datetime.now().strftime("%Y%m%d") datestr = datetime.datetime.now().strftime("%Y%m%d")
output = os.path.join(config['outdir'], f'Magisk-uninstaller-{datestr}.zip' output = op.join(config['outdir'], f'Magisk-uninstaller-{datestr}.zip'
if config['prettyName'] else 'magisk-uninstaller.zip') if config['prettyName'] else 'magisk-uninstaller.zip')
sign_zip(unsigned, output, args.release) sign_zip(unsigned, output, args.release)
rm(unsigned) rm(unsigned)
header('Output: ' + output) header('Output: ' + output)
@ -512,14 +534,66 @@ def cleanup(args):
if 'native' in args.target: if 'native' in args.target:
header('* Cleaning native') header('* Cleaning native')
system(ndk_build + ' -C native B_MAGISK=1 B_INIT=1 B_BOOT=1 B_BB=1 clean') rm_rf(op.join('native', 'out'))
shutil.rmtree(os.path.join('native', 'out'), ignore_errors=True) rm_rf(op.join('native', 'libs'))
rm_rf(op.join('native', 'obj'))
if 'java' in args.target: if 'java' in args.target:
header('* Cleaning java') header('* Cleaning java')
execv([gradlew, 'clean']) execv([gradlew, 'clean'])
def setup_ndk(args):
os_name = platform.system().lower()
url = f'https://dl.google.com/android/repository/android-ndk-r{ndk_ver}-{os_name}-x86_64.zip'
ndk_zip = url.split('/')[-1]
header(f'* Downloading {ndk_zip}')
with urllib.request.urlopen(url) as response, open(ndk_zip, 'wb') as out_file:
shutil.copyfileobj(response, out_file)
header('* Extracting NDK zip')
rm_rf(ndk_path)
with zipfile.ZipFile(ndk_zip, 'r') as zf:
for info in zf.infolist():
extracted_path = zf.extract(info, ndk_root)
vprint(f'Extracting {info.filename}')
if info.create_system == 3: # ZIP_UNIX_SYSTEM = 3
unix_attributes = info.external_attr >> 16
if unix_attributes:
os.chmod(extracted_path, unix_attributes)
mv(op.join(ndk_root, f'android-ndk-r{ndk_ver}'), ndk_path)
header('* Removing unnecessary files')
for dirname, subdirs, _ in os.walk(op.join(ndk_path, 'platforms')):
for plats in subdirs:
pp = op.join(dirname, plats)
rm_rf(pp)
mkdir(pp)
subdirs.clear()
rm_rf(op.join(ndk_path, 'sysroot'))
header('* Replacing API-16 static libs')
for arch in ['arm', 'i686']:
lib_dir = op.join(
ndk_path, 'toolchains', 'llvm', 'prebuilt', f'{os_name}-x86_64',
'sysroot', 'usr', 'lib', f'{arch}-linux-androideabi', '16')
src_dir = op.join('tools', 'ndk-bins', arch)
# Remove stupid macOS crap
rm(op.join(src_dir, '.DS_Store'))
for path in copy_tree(src_dir, lib_dir):
vprint(f'Replaced {path}')
# Rewrite source.properties
src_prop = op.join(ndk_path, 'source.properties')
props = parse_props(src_prop)
props['Pkg.Revision.orig'] = props['Pkg.Revision']
props['Pkg.Revision'] = '0.0.0'
with open(src_prop, 'w') as p:
for key, val in props.items():
print(f'{key} = {val}', file=p)
def build_all(args): def build_all(args):
vars(args)['target'] = [] vars(args)['target'] = []
build_stub(args) build_stub(args)
@ -572,6 +646,9 @@ clean_parser.add_argument(
'target', nargs='*', help='native, java, or empty to clean both') 'target', nargs='*', help='native, java, or empty to clean both')
clean_parser.set_defaults(func=cleanup) clean_parser.set_defaults(func=cleanup)
ndk_parser = subparsers.add_parser('ndk', help='setup Magisk NDK')
ndk_parser.set_defaults(func=setup_ndk)
if len(sys.argv) == 1: if len(sys.argv) == 1:
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)

View File

@ -2,6 +2,8 @@ apply plugin: 'com.android.library'
android { android {
ndkVersion '0.0.0'
externalNativeBuild { externalNativeBuild {
ndkBuild { ndkBuild {
path 'jni/Android.mk' path 'jni/Android.mk'

4
tools/ndk-bins/README.md Normal file
View File

@ -0,0 +1,4 @@
## Prebuilt Static Libraries
These binaries are copied straight from Google NDK r10e (`android-ndk-r10e/platforms/android-16`).
The reason why Magisk prefer to use these static libs is because they yield significantly smaller binaries.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tools/ndk-bins/arm/libc.a Normal file

Binary file not shown.

BIN
tools/ndk-bins/arm/libm.a Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tools/ndk-bins/i686/libc.a Normal file

Binary file not shown.

BIN
tools/ndk-bins/i686/libm.a Normal file

Binary file not shown.

Binary file not shown.

BIN
tools/ndk-bins/i686/libz.a Normal file

Binary file not shown.