From 494615d9a0274a7979ca72b4e5b1aae6914f4b5e Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Fri, 28 Feb 2025 16:14:07 -0800 Subject: [PATCH] Support ./build.py emulator with an APK argument --- build.py | 245 ++++++++++++++++++++++++------------------ docs/faq.md | 13 +-- scripts/avd_magisk.sh | 9 +- scripts/avd_patch.sh | 4 +- scripts/avd_test.sh | 4 +- scripts/cuttlefish.sh | 4 +- 6 files changed, 153 insertions(+), 126 deletions(-) diff --git a/build.py b/build.py index e44a75bf6..1c874d08e 100755 --- a/build.py +++ b/build.py @@ -2,7 +2,6 @@ import argparse import copy import glob -import lzma import multiprocessing import os import platform @@ -57,14 +56,6 @@ if is_windows: if not sys.version_info >= (3, 8): error("Requires Python 3.8+") -try: - sdk_path = Path(os.environ["ANDROID_HOME"]) -except KeyError: - try: - sdk_path = Path(os.environ["ANDROID_SDK_ROOT"]) - except KeyError: - error("Please set Android SDK path to environment variable ANDROID_HOME") - cpu_count = multiprocessing.cpu_count() os_name = platform.system().lower() @@ -80,17 +71,6 @@ default_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"} support_targets = default_targets | {"resetprop"} rust_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"} -# Common paths -ndk_root = sdk_path / "ndk" -ndk_path = ndk_root / "magisk" -ndk_build = ndk_path / "ndk-build" -rust_bin = ndk_path / "toolchains" / "rust" / "bin" -llvm_bin = ndk_path / "toolchains" / "llvm" / "prebuilt" / f"{os_name}-x86_64" / "bin" -cargo = rust_bin / "cargo" -gradlew = Path.cwd() / "gradlew" -adb_path = sdk_path / "platform-tools" / "adb" -native_gen_path = Path("native", "out", "generated").resolve() - # Global vars config = {} args = {} @@ -162,10 +142,6 @@ def cmd_out(cmds: list): ) -def xz(data): - return lzma.compress(data, preset=9, check=lzma.CHECK_NONE) - - ############### # Build Native ############### @@ -256,6 +232,7 @@ def build_cpp_src(targets: set): def run_cargo(cmds): + ensure_paths() env = os.environ.copy() env["PATH"] = f'{rust_bin}{os.pathsep}{env["PATH"]}' env["CARGO_BUILD_RUSTC"] = str(rust_bin / f"rustc{EXE_EXT}") @@ -333,6 +310,7 @@ def dump_flag_header(): flag_txt += f'#define MAGISK_VER_CODE {config["versionCode"]}\n' flag_txt += f"#define MAGISK_DEBUG {0 if args.release else 1}\n" + native_gen_path = Path("native", "out", "generated") native_gen_path.mkdir(mode=0o755, parents=True, exist_ok=True) write_if_diff(native_gen_path / "flags.h", flag_txt) @@ -342,6 +320,8 @@ def dump_flag_header(): def build_native(): + ensure_paths() + # Verify NDK install try: with open(Path(ndk_path, "ONDK_VERSION"), "r") as ondk_ver: @@ -401,13 +381,14 @@ def find_jdk(): if no_jdk: error( "Please set Android Studio's path to environment variable ANDROID_STUDIO,\n" - + "or install JDK 17 and make sure 'javac' is available in PATH" + + "or install JDK 21 and make sure 'javac' is available in PATH" ) return env def build_apk(module: str): + ensure_paths() env = find_jdk() build_type = "Release" if args.release else "Debug" @@ -479,6 +460,7 @@ def build_test(): def cleanup(): + ensure_paths() support_targets = {"native", "cpp", "rust", "app"} if args.targets: targets = set(args.targets) & support_targets @@ -540,6 +522,7 @@ def cargo_cli(): def setup_ndk(): + ensure_paths() ndk_ver = config["ondkVersion"] url = f"https://github.com/topjohnwu/ondk/releases/download/{ndk_ver}/ondk-{ndk_ver}-{os_name}.tar.xz" ndk_archive = url.split("/")[-1] @@ -558,79 +541,6 @@ def setup_ndk(): mv(ondk_path, ndk_path) -def push_files(script): - abi = cmd_out([adb_path, "shell", "getprop", "ro.product.cpu.abi"]) - if not abi: - error("Cannot detect emulator ABI") - - apk = Path( - config["outdir"], ("app-release.apk" if args.release else "app-debug.apk") - ) - - # Extract busybox from APK - busybox = Path(config["outdir"], "busybox") - with ZipFile(apk) as zf: - with zf.open(f"lib/{abi}/libbusybox.so") as libbb: - with open(busybox, "wb") as bb: - bb.write(libbb.read()) - - try: - proc = execv([adb_path, "push", busybox, script, "/data/local/tmp"]) - if proc.returncode != 0: - error("adb push failed!") - finally: - rm_rf(busybox) - - proc = execv([adb_path, "push", apk, "/data/local/tmp/magisk.apk"]) - if proc.returncode != 0: - error("adb push failed!") - - -def setup_avd(): - if not args.skip: - build_all() - - header("* Setting up emulator") - - push_files(Path("scripts", "avd_magisk.sh")) - - proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_magisk.sh"]) - if proc.returncode != 0: - error("avd_magisk.sh failed!") - - -def patch_avd_file(): - if not args.skip: - build_all() - - input = Path(args.image) - if args.output: - output = Path(args.output) - else: - output = input.parent / f"{input.name}.magisk" - - src_file = f"/data/local/tmp/{input.name}" - out_file = f"{src_file}.magisk" - - header(f"* Patching {input.name}") - - push_files(Path("scripts", "avd_patch.sh")) - - proc = execv([adb_path, "push", input, "/data/local/tmp"]) - if proc.returncode != 0: - error("adb push failed!") - - proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_patch.sh", src_file]) - if proc.returncode != 0: - error("avd_patch.sh failed!") - - proc = execv([adb_path, "pull", out_file, output]) - if proc.returncode != 0: - error("adb pull failed!") - - header(f"Output: {output}") - - def setup_rustup(): wrapper_dir = Path(args.wrapper_dir) rm_rf(wrapper_dir) @@ -660,10 +570,125 @@ def setup_rustup(): ################## -# Config and args +# AVD and testing ################## +def push_files(script): + if args.build: + build_all() + ensure_adb() + + abi = cmd_out([adb_path, "shell", "getprop", "ro.product.cpu.abi"]) + if not abi: + error("Cannot detect emulator ABI") + + if args.apk: + apk = Path(args.apk) + else: + apk = Path( + config["outdir"], ("app-release.apk" if args.release else "app-debug.apk") + ) + + # Extract busybox from APK + busybox = Path(config["outdir"], "busybox") + with ZipFile(apk) as zf: + with zf.open(f"lib/{abi}/libbusybox.so") as libbb: + with open(busybox, "wb") as bb: + bb.write(libbb.read()) + + try: + proc = execv([adb_path, "push", busybox, script, "/data/local/tmp"]) + if proc.returncode != 0: + error("adb push failed!") + finally: + rm_rf(busybox) + + proc = execv([adb_path, "push", apk, "/data/local/tmp/magisk.apk"]) + if proc.returncode != 0: + error("adb push failed!") + + +def setup_avd(): + header("* Setting up emulator") + + push_files(Path("scripts", "avd_magisk.sh")) + + proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_magisk.sh"]) + if proc.returncode != 0: + error("avd_magisk.sh failed!") + + +def patch_avd_file(): + input = Path(args.image) + output = Path(args.output) + + header(f"* Patching {input.name}") + + push_files(Path("scripts", "avd_patch.sh")) + + proc = execv([adb_path, "push", input, "/data/local/tmp"]) + if proc.returncode != 0: + error("adb push failed!") + + src_file = f"/data/local/tmp/{input.name}" + out_file = f"{src_file}.magisk" + + proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_patch.sh", src_file]) + if proc.returncode != 0: + error("avd_patch.sh failed!") + + proc = execv([adb_path, "pull", out_file, output]) + if proc.returncode != 0: + error("adb pull failed!") + + header(f"Output: {output}") + + +########################## +# Config, paths, argparse +########################## + + +def ensure_paths(): + global sdk_path, ndk_root, ndk_path, ndk_build, rust_bin + global llvm_bin, cargo, gradlew, adb_path, native_gen_path + + # Skip if already initialized + if "sdk_path" in globals(): + return + + try: + sdk_path = Path(os.environ["ANDROID_HOME"]) + except KeyError: + try: + sdk_path = Path(os.environ["ANDROID_SDK_ROOT"]) + except KeyError: + error("Please set Android SDK path to environment variable ANDROID_HOME") + + ndk_root = sdk_path / "ndk" + ndk_path = ndk_root / "magisk" + ndk_build = ndk_path / "ndk-build" + rust_bin = ndk_path / "toolchains" / "rust" / "bin" + llvm_bin = ( + ndk_path / "toolchains" / "llvm" / "prebuilt" / f"{os_name}-x86_64" / "bin" + ) + cargo = rust_bin / "cargo" + adb_path = sdk_path / "platform-tools" / "adb" + gradlew = Path.cwd() / "gradlew" + + +# We allow using several functionality with only ADB +def ensure_adb(): + global adb_path + if "adb_path" not in globals(): + adb_path = shutil.which("adb") + if not adb_path: + error("Command 'adb' cannot be found in PATH") + else: + adb_path = Path(adb_path) + + def parse_props(file): props = {} with open(file, "r") as f: @@ -761,17 +786,19 @@ def parse_args(): ndk_parser = subparsers.add_parser("ndk", help="setup Magisk NDK") emu_parser = subparsers.add_parser("emulator", help="setup AVD for development") + emu_parser.add_argument("apk", help="a Magisk APK to use", nargs="?") emu_parser.add_argument( - "-s", "--skip", action="store_true", help="skip building binaries and the app" + "-b", "--build", action="store_true", help="build before patching" ) avd_patch_parser = subparsers.add_parser( "avd_patch", help="patch AVD ramdisk.img or init_boot.img" ) avd_patch_parser.add_argument("image", help="path to ramdisk.img or init_boot.img") - avd_patch_parser.add_argument("output", help="optional output file name", nargs="?") + avd_patch_parser.add_argument("output", help="output file name") + avd_patch_parser.add_argument("--apk", help="a Magisk APK to use") avd_patch_parser.add_argument( - "-s", "--skip", action="store_true", help="skip building binaries and the app" + "-b", "--build", action="store_true", help="build before patching" ) cargo_parser = subparsers.add_parser( @@ -807,7 +834,13 @@ def parse_args(): return parser.parse_args() -args = parse_args() -load_config() -vars(args)["force_out"] = False -args.func() +def main(): + global args + args = parse_args() + load_config() + vars(args)["force_out"] = False + args.func() + + +if __name__ == "__main__": + main() diff --git a/docs/faq.md b/docs/faq.md index 00490428c..b9e48fae9 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -7,9 +7,10 @@ If you have USB debugging enabled in developer options, connect your phone to th If unfortunately you do not have USB debugging enabled you can boot using the Safe Mode key combo to cause Magisk to create an empty file named 'disable' in modules directories which disables modules when next booted with Magisk. Most modern Android devices support such a special key combo at boot to enter system Safe Mode as an emergency option, but **please note** that Magisk's key combo detection occurs _earlier_ than system detection so the key combo timing indicated by many online guides may need to be altered to activate Magisk's Safe Mode. (It's possible to activate system Safe Mode but not Magisk Safe Mode and vice versa.) The following details should ensure that modules are properly disabled: -1) Many online guides for entering Safe Mode say 'When the animated logo appears, press and hold the volume down button until the system boots' or similar. This may actually be _too late_ for Magisk detection however and result in activating system Safe Mode but modules are not disabled. -2) By pressing the volume down button some seconds before the animation and releasing it as soon as the boot animation appears, Magisk's Safe Mode should be activated without activating system Safe Mode (thus avoiding disabling other device and app settings) and the device should then simply boot to normal system with modules disabled. -3) By pressing the volume down button some seconds before the animation and holding it until the system boots, both Magisk's Safe Mode and system Safe Mode should be activated. Next, after booting back to normal system, modules will be disabled. + +1. Many online guides for entering Safe Mode say 'When the animated logo appears, press and hold the volume down button until the system boots' or similar. This may actually be _too late_ for Magisk detection however and result in activating system Safe Mode but modules are not disabled. +2. By pressing the volume down button some seconds before the animation and releasing it as soon as the boot animation appears, Magisk's Safe Mode should be activated without activating system Safe Mode (thus avoiding disabling other device and app settings) and the device should then simply boot to normal system with modules disabled. +3. By pressing the volume down button some seconds before the animation and holding it until the system boots, both Magisk's Safe Mode and system Safe Mode should be activated. Next, after booting back to normal system, modules will be disabled. ### Q: Why is X app detecting root? @@ -23,6 +24,6 @@ When you open the hidden Magisk app, it will offer you the option to create a sh ### Q: How to use Magisk in the emulator? -[avd_magisk.sh](../scripts/avd_magisk.sh) script can run Magisk in the emulator, Magisk will be lost after reboot, so please re-execute the script instead of tapping the reboot button. -Only official Android Virtual Device (AVD) is supported, other emulators may work, but emulators without SELinux will never be supported. -For more information, read the script comments. +With the emulator running and accessible via ADB, run `./build.py emulator ` to temporarily install Magisk on to the emulator. The patch is not persistent, meaning Magisk will be lost after a reboot, so re-execute the script to emulate a reboot if required. + +The script is only tested on the official Android Virtual Device (AVD) shipped alongside Android Studio; other emulators may work, but the emulator must have SELinux enabled. diff --git a/scripts/avd_magisk.sh b/scripts/avd_magisk.sh index 968f32de5..153e9d183 100755 --- a/scripts/avd_magisk.sh +++ b/scripts/avd_magisk.sh @@ -2,14 +2,7 @@ # AVD Magisk Setup ##################################################################### # -# Support API level: 23 - 34 -# -# Push files to AVD: -# /data/local/tmp/magisk.apk -# /data/local/tmp/busybox (extract from apk) -# /data/local/tmp/avd_magisk.sh (this file) -# Then execute, you must use non-interactive shell: -# adb shell sh /data/local/tmp/avd_magisk.sh +# Support API level: 23 - 35 # # For developing Magisk, just use: # ./build.py emulator diff --git a/scripts/avd_patch.sh b/scripts/avd_patch.sh index c9b7b4e26..d406f9440 100644 --- a/scripts/avd_patch.sh +++ b/scripts/avd_patch.sh @@ -2,7 +2,7 @@ # AVD MagiskInit Setup ##################################################################### # -# Support API level: 23 - 34 +# Support API level: 23 - 35 # # With an emulator booted and accessible via ADB, usage: # ./build.py avd_patch path/to/booted/avd-image/ramdisk.img @@ -18,7 +18,7 @@ # rootfs w/o early mount: API 23 - 25 # rootfs with early mount: API 26 - 27 # Legacy system-as-root: API 28 -# 2 stage init: API 29 - 34 +# 2 stage init: API 29 - 35 ##################################################################### if [ ! -f /system/build.prop ]; then diff --git a/scripts/avd_test.sh b/scripts/avd_test.sh index c1f31b149..a43151f7e 100755 --- a/scripts/avd_test.sh +++ b/scripts/avd_test.sh @@ -146,7 +146,7 @@ test_main() { if [ -z "$AVD_TEST_SKIP_DEBUG" ]; then # Patch and test debug build - ./build.py avd_patch -s "$ramdisk" magisk_patched.img + ./build.py avd_patch "$ramdisk" magisk_patched.img kill -INT $emu_pid wait $emu_pid test_emu debug $api @@ -154,7 +154,7 @@ test_main() { if [ -z "$AVD_TEST_SKIP_RELEASE" ]; then # Patch and test release build - ./build.py -r avd_patch -s "$ramdisk" magisk_patched.img + ./build.py -r avd_patch "$ramdisk" magisk_patched.img kill -INT $emu_pid wait $emu_pid test_emu release $api diff --git a/scripts/cuttlefish.sh b/scripts/cuttlefish.sh index e55642897..5375d3728 100755 --- a/scripts/cuttlefish.sh +++ b/scripts/cuttlefish.sh @@ -84,11 +84,11 @@ test_main() { adb wait-for-device # Patch and test debug build - ./build.py avd_patch -s "$CF_HOME/init_boot.img" magisk_patched.img + ./build.py avd_patch "$CF_HOME/init_boot.img" magisk_patched.img test_cf debug # Patch and test release build - ./build.py -r avd_patch -s "$CF_HOME/init_boot.img" magisk_patched.img + ./build.py -r avd_patch "$CF_HOME/init_boot.img" magisk_patched.img test_cf release # Cleanup