Files
Magisk/scripts/env.py
2026-04-10 04:13:15 +00:00

168 lines
4.7 KiB
Python
Executable File

#!/usr/bin/env python3
import functools
import multiprocessing
import os
import platform
import sys
import shutil
import subprocess
from pathlib import Path
from typing import NoReturn
ondk_version = "r29.5"
def color_print(code, str):
if no_color:
print(str)
else:
str = str.replace("\n", f"\033[0m\n{code}")
print(f"{code}{str}\033[0m")
def error(str) -> NoReturn:
color_print("\033[41;39m", f"\n! {str}\n")
sys.exit(1)
def header(str):
color_print("\033[44;39m", f"\n{str}\n")
class Paths:
def __init__(self):
try:
self.sdk = Path(os.environ["ANDROID_HOME"])
except KeyError:
try:
self.sdk = Path(os.environ["ANDROID_SDK_ROOT"])
except KeyError:
error(
"Please set Android SDK path to environment variable ANDROID_HOME"
)
self.ndk = self.sdk / "ndk" / "magisk"
self.ndk_build = self.ndk / "ndk-build"
self.rust_sysroot = self.ndk / "toolchains" / "rust"
self.adb = self.sdk / "platform-tools" / "adb"
project_root = Path(__file__).resolve().parent.parent
self.gradlew = project_root / "app" / "gradlew"
@functools.cache
def paths() -> Paths:
return Paths()
def run_once(func):
def wrapper(*args, **kwargs):
if not wrapper.has_run:
wrapper.has_run = True
return func(*args, **kwargs)
wrapper.has_run = False
return wrapper
@run_once
def ensure_toolchain():
# Verify NDK install
try:
with open(paths().ndk / "ONDK_VERSION", "r") as ondk_ver:
assert ondk_ver.read().strip(" \t\r\n") == ondk_version
except:
error('Unmatched NDK. Please install/upgrade NDK with "build.py ndk"')
if sccache := shutil.which("sccache"):
os.environ["RUSTC_WRAPPER"] = sccache
os.environ["NDK_CCACHE"] = sccache
os.environ["CARGO_INCREMENTAL"] = "0"
if ccache := shutil.which("ccache"):
os.environ["NDK_CCACHE"] = ccache
@run_once
def ensure_cargo():
ensure_toolchain()
os.environ["CARGO_BUILD_RUSTFLAGS"] = f"-Z threads={min(8, cpu_count)}"
if shutil.which("rustup"):
# Go through rustup proxies by default if available
os.environ["RUSTUP_TOOLCHAIN"] = str(paths().rust_sysroot)
else:
os.environ["PATH"] = (
f"{paths().rust_sysroot / "bin"}{os.pathsep}{os.environ["PATH"]}"
)
# Cargo calls executables in $RUSTROOT/lib/rustlib/$TRIPLE/bin, we need
# to make sure the runtime linker also search $RUSTROOT/lib for libraries.
# This is only required on Unix, as Windows search dlls from PATH.
if os_name == "darwin":
os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = str(paths().rust_sysroot / "lib")
elif os_name == "linux":
os.environ["LD_LIBRARY_PATH"] = str(paths().rust_sysroot / "lib")
@run_once
def ensure_jdk():
if "ANDROID_STUDIO" in os.environ:
studio = os.environ["ANDROID_STUDIO"]
jbr = Path(studio, "jbr")
if not jbr.exists():
jbr = Path(studio, "Contents", "jbr", "Contents", "Home")
if jbr.exists():
os.environ["PATH"] = f'{jbr / "bin"}{os.pathsep}{os.environ["PATH"]}'
os.environ["JAVA_HOME"] = str(jbr)
os.environ["JDK_HOME"] = str(jbr)
no_jdk = False
try:
proc = subprocess.run(
"javac -version",
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
shell=True,
)
output = proc.stdout.strip().decode("utf-8")
no_jdk = proc.returncode != 0 and output.startswith("javac 21")
except FileNotFoundError:
no_jdk = True
if no_jdk:
error(
"Please set Android Studio's path to environment variable ANDROID_STUDIO,\n"
+ "or install JDK 21 and make sure 'javac' is available in PATH"
)
# OS detection
os_name = platform.system().lower()
is_windows = False
if os_name != "linux" and os_name != "darwin":
# It's possible we're using MSYS/Cygwin/MinGW, treat them all as Windows
is_windows = True
os_name = "windows"
EXE_EXT = ".exe" if is_windows else ""
no_color = False
if is_windows:
try:
import colorama
colorama.init()
except ImportError:
# We can't do ANSI color codes in terminal on Windows without colorama
no_color = True
# Environment detection
if not sys.version_info >= (3, 8):
error("Requires Python 3.8+")
cpu_count = multiprocessing.cpu_count()
# When directly invoked, make it a command wrapper
if __name__ == "__main__":
ensure_cargo()
ensure_jdk()
subprocess.run(sys.argv[1:])