Setup preliminary rust infrastructure

This commit is contained in:
topjohnwu 2022-06-30 14:50:21 -07:00
parent 0b26882fce
commit 26116ac414
21 changed files with 329 additions and 66 deletions

3
.gitmodules vendored
View File

@ -40,3 +40,6 @@
[submodule "zopfli"]
path = native/jni/external/zopfli
url = https://github.com/google/zopfli.git
[submodule "cxx-rs"]
path = native/jni/external/cxx-rs
url = https://github.com/topjohnwu/cxx.git

View File

@ -51,7 +51,7 @@ For Magisk app crashes, record and upload the logcat when the crash occurs.
- Run `./build.py ndk` to let the script download and install NDK for you
- To start building, run `build.py` to see your options. \
For each action, use `-h` to access help (e.g. `./build.py all -h`)
- To start development, open the project with Android Studio. The IDE can be used for both app (Kotlin/Java) and native (C++/C) sources.
- To start development, open the project with Android Studio. The IDE can be used for both app (Kotlin/Java) and native sources.
- Optionally, set custom configs with `config.prop`. A sample `config.prop.sample` is provided.
## Signing and Distribution

138
build.py
View File

@ -37,10 +37,10 @@ def vprint(str):
is_windows = os.name == 'nt'
is_ci = 'CI' in os.environ and os.environ['CI'] == 'true'
EXE_EXT = '.exe' if is_windows else ''
if not is_ci and is_windows:
import colorama
colorama.init()
# Environment checks
@ -58,16 +58,20 @@ except FileNotFoundError:
cpu_count = multiprocessing.cpu_count()
archs = ['armeabi-v7a', 'x86', 'arm64-v8a', 'x86_64']
triples = ['armv7a-linux-androideabi', 'i686-linux-android', 'aarch64-linux-android', 'x86_64-linux-android']
default_targets = ['magisk', 'magiskinit', 'magiskboot', 'magiskpolicy', 'busybox']
support_targets = default_targets + ['resetprop', 'test']
rust_targets = ['magisk', 'magiskinit', 'magiskboot', 'magiskpolicy']
sdk_path = os.environ['ANDROID_SDK_ROOT']
ndk_root = op.join(sdk_path, 'ndk')
ndk_path = op.join(ndk_root, 'magisk')
ndk_build = op.join(ndk_path, 'ndk-build')
rust_bin = op.join(ndk_path, 'toolchains', 'rust', 'bin')
cargo = op.join(rust_bin, 'cargo' + EXE_EXT)
gradlew = op.join('.', 'gradlew' + ('.bat' if is_windows else ''))
adb_path = op.join(sdk_path, 'platform-tools', 'adb' + ('.exe' if is_windows else ''))
native_gen_path = op.join('native', 'out', 'generated')
adb_path = op.join(sdk_path, 'platform-tools', 'adb' + EXE_EXT)
native_gen_path = op.realpath(op.join('native', 'out', 'generated'))
# Global vars
config = {}
@ -123,16 +127,17 @@ def mkdir_p(path, mode=0o755):
os.makedirs(path, mode, exist_ok=True)
def execv(cmd):
return subprocess.run(cmd, stdout=STDOUT)
def execv(cmd, env=None):
return subprocess.run(cmd, stdout=STDOUT, env=env)
def system(cmd):
return subprocess.run(cmd, shell=True, stdout=STDOUT)
def cmd_out(cmd):
return subprocess.check_output(cmd).strip().decode('utf-8')
def cmd_out(cmd, env=None):
return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=env) \
.stdout.strip().decode('utf-8')
def xz(data):
@ -180,15 +185,6 @@ def load_config(args):
STDOUT = None if args.verbose else subprocess.DEVNULL
def collect_binary():
for arch in archs:
mkdir_p(op.join('native', 'out', arch))
for bin in support_targets + ['libpreload.so']:
source = op.join('native', 'libs', arch, bin)
target = op.join('native', 'out', arch, bin)
mv(source, target)
def clean_elf():
if is_windows:
elf_cleaner = op.join('tools', 'elf-cleaner.exe')
@ -203,48 +199,6 @@ def clean_elf():
execv(args)
def find_build_tools():
global build_tools
if build_tools:
return build_tools
build_tools_root = op.join(os.environ['ANDROID_SDK_ROOT'], 'build-tools')
ls = os.listdir(build_tools_root)
# Use the latest build tools available
ls.sort()
build_tools = op.join(build_tools_root, ls[-1])
return build_tools
# Unused but keep this code
def sign_zip(unsigned):
if 'keyStore' not in config:
return
msg = '* Signing APK'
apksigner = op.join(find_build_tools(), 'apksigner' + ('.bat' if is_windows else ''))
exec_args = [apksigner, 'sign',
'--ks', config['keyStore'],
'--ks-pass', f'pass:{config["keyStorePass"]}',
'--ks-key-alias', config['keyAlias'],
'--key-pass', f'pass:{config["keyPass"]}',
'--v1-signer-name', 'CERT',
'--v4-signing-enabled', 'false']
if unsigned.endswith('.zip'):
msg = '* Signing zip'
exec_args.extend(['--min-sdk-version', '17',
'--v2-signing-enabled', 'false',
'--v3-signing-enabled', 'false'])
exec_args.append(unsigned)
header(msg)
proc = execv(exec_args)
if proc.returncode != 0:
error('Signing failed!')
def binary_dump(src, var_name):
out_str = f'constexpr unsigned char {var_name}[] = {{'
for i, c in enumerate(xz(src.read())):
@ -261,7 +215,70 @@ def run_ndk_build(flags):
if proc.returncode != 0:
error('Build binary failed!')
os.chdir('..')
collect_binary()
for arch in archs:
for tgt in support_targets + ['libpreload.so']:
source = op.join('native', 'libs', arch, tgt)
target = op.join('native', 'out', arch, tgt)
mv(source, target)
def run_cargo_build(args):
os.chdir(op.join('native', 'rust'))
targets = set(args.target) & set(rust_targets)
env = os.environ.copy()
env['PATH'] = f'{rust_bin}:{env["PATH"]}'
# Install cxxbridge and generate C++ bindings
native_out = op.join('..', '..', 'native', 'out')
local_cargo_root = op.join(native_out, '.cargo')
mkdir_p(local_cargo_root)
cmds = [cargo, 'install', '--root', local_cargo_root, 'cxxbridge-cmd']
if not args.verbose:
cmds.append('-q')
proc = execv(cmds, env)
if proc.returncode != 0:
error('cxxbridge-cmd installation failed!')
cxxbridge = op.join(local_cargo_root, 'bin', 'cxxbridge' + EXE_EXT)
mkdir(native_gen_path)
for p in ['base', 'boot', 'core', 'init', 'sepolicy']:
text = cmd_out([cxxbridge, op.join(p, 'src', 'lib.rs')])
write_if_diff(op.join(native_gen_path, f'{p}-rs.cpp'), text)
text = cmd_out([cxxbridge, '--header', op.join(p, 'src', 'lib.rs')])
write_if_diff(op.join(native_gen_path, f'{p}-rs.hpp'), text)
# Start building the actual build commands
cmds = [cargo, 'build', '-Z', 'build-std=std,panic_abort',
'-Z', 'build-std-features=panic_immediate_abort']
for target in targets:
cmds.append('-p')
cmds.append(target)
rust_out = 'debug'
if args.release:
cmds.append('-r')
rust_out = 'release'
if not args.verbose:
cmds.append('-q')
os_name = platform.system().lower()
llvm_bin = op.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', f'{os_name}-x86_64', 'bin')
env['TARGET_CC'] = op.join(llvm_bin, 'clang' + EXE_EXT)
env['RUSTFLAGS'] = '-Clinker-plugin-lto'
for (arch, triple) in zip(archs, triples):
env['TARGET_CFLAGS'] = f'--target={triple}21'
rust_triple = 'thumbv7neon-linux-androideabi' if triple.startswith('armv7') else triple
proc = execv([*cmds, '--target', rust_triple], env)
if proc.returncode != 0:
error('Build binary failed!')
arch_out = op.join(native_out, arch)
mkdir(arch_out)
for tgt in targets:
source = op.join('target', rust_triple, rust_out, f'lib{tgt}.a')
target = op.join(arch_out, f'lib{tgt}-rs.a')
mv(source, target)
os.chdir(op.join('..', '..'))
def write_if_diff(file_name, text):
@ -326,6 +343,8 @@ def build_binary(args):
header('* Building binaries: ' + ' '.join(args.target))
run_cargo_build(args)
dump_flag_header()
flag = ''
@ -402,6 +421,7 @@ def cleanup(args):
rm_rf(op.join('native', 'out'))
rm_rf(op.join('native', 'libs'))
rm_rf(op.join('native', 'obj'))
rm_rf(op.join('native', 'rust', 'target'))
if 'java' in args.target:
header('* Cleaning java')

34
native/README.md Normal file
View File

@ -0,0 +1,34 @@
# Native Development
## Prerequisite
Install the NDK required to build and develop Magisk with `./build.py ndk`. The NDK will be installed to `$ANDROID_SDK_ROOT/ndk/magisk`. You don't need to manually install a Rust toolchain with `rustup`, as the NDK installed already has a Rust toolchain bundled.
## Code Paths
- `jni`: Magisk's code in C++
- `jni/external`: external dependencies, mostly submodules
- `rust`: Magisk's code in Rust
- `src`: irrelevant, only exists to setup a native Android Studio project
## Build Configs
All C/C++ code and its dependencies are built with [`ndk-build`](https://developer.android.com/ndk/guides/ndk-build) and configured with several `*.mk` files scatterred in many places.
The `rust` folder is a proper Cargo workspace, and all Rust code is built with `cargo` just like any other Rust projects.
## Rust + C/C++
To reduce complexity involved in linking, all Rust code is built as `staticlib` and linked to C++ targets to ensure our final product is built with an officially supported NDK build system. Each C++ target can at most link to **one** Rust `staticlib` or else multiple definitions error will occur.
We use the [`cxx`](https://cxx.rs) project for interop between Rust and C++. Although cxx supports interop in both directions, for Magisk, it is strongly advised to avoid calling C++ functions in Rust; if some functionality required in Rust is already implemented in C++, the desired solution is to port the C++ implementation into Rust and export the migrated function back to C++.
## Development / IDE
All C++ code should be recognized and properly indexed by Android Studio out of the box. For Rust:
- Install the [Rust plugin](https://www.jetbrains.com/rust/) in Android Studio
- In Preferences > Languages & Frameworks > Rust, set `$ANDROID_SDK_ROOT/ndk/magisk/toolchains/rust/bin` as the toolchain location
- Open `native/rust/Cargo.toml`, and select "Attach" in the "No Cargo projects found" banner
Note: run `./build.py binary` before developing to make sure generated code is created.

25
native/jni/Android-rs.mk Normal file
View File

@ -0,0 +1,25 @@
LOCAL_PATH := $(call my-dir)
###########################
# Rust compilation outputs
###########################
include $(CLEAR_VARS)
LOCAL_MODULE := magisk-rs
LOCAL_SRC_FILES := ../out/$(TARGET_ARCH_ABI)/libmagisk-rs.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := boot-rs
LOCAL_SRC_FILES := ../out/$(TARGET_ARCH_ABI)/libmagiskboot-rs.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := init-rs
LOCAL_SRC_FILES := ../out/$(TARGET_ARCH_ABI)/libmagiskinit-rs.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := policy-rs
LOCAL_SRC_FILES := ../out/$(TARGET_ARCH_ABI)/libmagiskpolicy-rs.a
include $(PREBUILT_STATIC_LIBRARY)

View File

@ -14,7 +14,8 @@ LOCAL_STATIC_LIBRARIES := \
libsystemproperties \
libphmap \
libxhook \
libmincrypt
libmincrypt \
libmagisk-rs
LOCAL_SRC_FILES := \
core/applets.cpp \
@ -68,7 +69,8 @@ LOCAL_STATIC_LIBRARIES := \
libbase \
libcompat \
libpolicy \
libxz
libxz \
libinit-rs
LOCAL_SRC_FILES := \
init/init.cpp \
@ -95,7 +97,8 @@ LOCAL_STATIC_LIBRARIES := \
libbz2 \
libfdt \
libz \
libzopfli
libzopfli \
libboot-rs
LOCAL_SRC_FILES := \
boot/main.cpp \
@ -119,7 +122,8 @@ LOCAL_MODULE := magiskpolicy
LOCAL_STATIC_LIBRARIES := \
libbase \
libbase \
libpolicy
libpolicy \
libpolicy-rs
LOCAL_SRC_FILES := sepolicy/main.cpp
@ -135,7 +139,8 @@ LOCAL_STATIC_LIBRARIES := \
libbase \
libcompat \
libnanopb \
libsystemproperties
libsystemproperties \
libmagisk-rs
LOCAL_SRC_FILES := \
core/applet_stub.cpp \
@ -181,6 +186,7 @@ LOCAL_SRC_FILES := \
sepolicy/statement.cpp
include $(BUILD_STATIC_LIBRARY)
include jni/Android-rs.mk
include jni/base/Android.mk
include jni/external/Android.mk

View File

@ -15,7 +15,8 @@ LOCAL_SRC_FILES := \
selinux.cpp \
logging.cpp \
xwrap.cpp \
stream.cpp
stream.cpp \
../external/cxx-rs/src/cxx.cc
include $(BUILD_STATIC_LIBRARY)
# All static executables should link with libcompat

1
native/jni/external/cxx-rs vendored Submodule

@ -0,0 +1 @@
Subproject commit 650e64bc3970625f580a61ad9cfc65faa5cdd948

1
native/rust/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target/

99
native/rust/Cargo.lock generated Normal file
View File

@ -0,0 +1,99 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "base"
version = "0.1.0"
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cxx"
version = "1.0.69"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.69"
[[package]]
name = "cxxbridge-macro"
version = "1.0.69"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "magisk"
version = "0.1.0"
dependencies = [
"base",
"cxx",
]
[[package]]
name = "magiskboot"
version = "0.1.0"
dependencies = [
"base",
]
[[package]]
name = "magiskinit"
version = "0.1.0"
dependencies = [
"base",
]
[[package]]
name = "magiskpolicy"
version = "0.1.0"
dependencies = [
"base",
]
[[package]]
name = "proc-macro2"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"

26
native/rust/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[workspace]
members = [
"base",
"boot",
"core",
"init",
"sepolicy",
]
[profile.dev]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = true
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = true
[patch.crates-io]
cxx = { path = "../jni/external/cxx-rs" }

View File

@ -0,0 +1,6 @@
[package]
name = "base"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

View File

@ -0,0 +1,10 @@
[package]
name = "magiskboot"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[dependencies]
base = { path = "../base" }

View File

View File

@ -0,0 +1,11 @@
[package]
name = "magisk"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[dependencies]
base = { path = "../base" }
cxx = "1.0.69"

View File

View File

@ -0,0 +1,10 @@
[package]
name = "magiskinit"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[dependencies]
base = { path = "../base" }

View File

View File

@ -0,0 +1,10 @@
[package]
name = "magiskpolicy"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib", "rlib"]
[dependencies]
base = { path = "../base" }

View File