#!/usr/bin/env bash usage() { cat << EOF Usage: $(basename "$0") [COMMAND] Commands: build Build the kernel (default) menuconfig Run menuconfig and save customizations repack Repack stock boot image with custom kernel download Download latest LineageOS boot image help Show this help message Examples: $(basename "$0") # Build the kernel $(basename "$0") build # Build the kernel $(basename "$0") menuconfig # Configure kernel interactively $(basename "$0") repack # Repack stock_boot.img with new kernel (auto-downloads if missing) $(basename "$0") download # Download latest LineageOS boot.img for dodge EOF exit 0 } set_vars() { # Ensure that everything is contained within the project. # We expect that we're going to be running out of [PROJECT]/meta, so we hop up one directory. BASEDIR=$(realpath "$(dirname -- "$(realpath -- "${BASH_SOURCE[0]}")")/..") # Build toolchain locations CLANGDIR="${BASEDIR}/prebuilts/clang/host/linux-x86/clang-r547379" GCCDIR="${BASEDIR}/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9" GCCDIR32="${BASEDIR}/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9" KERNELBUILDTOOLS="${BASEDIR}/prebuilts/kernel-build-tools/linux-x86" # Array for all of the custom paths introduced by prebuilts PREBUILTS_PATH=( "${CLANGDIR}/bin" "${GCCDIR}/bin" "${KERNELBUILDTOOLS}/bin" ) # Common options used for `make`, particularly for cross-compile MAKEOPTS=( O="${BASEDIR}/work" ARCH=arm64 PATH="${CLANGDIR}/bin:${GCCDIR}/bin:${KERNELBUILDTOOLS}/bin:${PATH}" LD_LIBRARY_PATH="${CLANGDIR}/lib" LLVM=1 CROSS_COMPILE="${GCCDIR}/bin/aarch64-linux-android-" CROSS_COMPILE_ARM32="${GCCDIR32}/bin/arm-linux-androideabi-" CLANG_TRIPLE=aarch64-linux-gnu- ) # Environment tools that are already somewhere in the project so we'll use them instead of adding extra dependencies BUSYBOX="${BASEDIR}/prebuilts/magisk/lib/x86_64/libbusybox.so" MAGISKBOOT="${BASEDIR}/prebuilts/magisk/lib/x86_64/libmagiskboot.so" PYTHON="${BASEDIR}/prebuilts/clang/host/linux-x86/clang-r547379/python3/bin/python3" } check_tools() { # Ensure that we have everything we need to work before we get started. TOOLS_NEEDED=() [ ! -x "$(command -v make)" ] && TOOLS_NEEDED+=(make) [ ! -x "$(command -v patch)" ] && TOOLS_NEEDED+=(patch) [ ! -x "$(command -v zip)" ] && TOOLS_NEEDED+=(zip) if [ "${#TOOLS_NEEDED[@]}" -gt 0 ]; then echo "You are missing: ${TOOLS_NEEDED[@]}" exit 1 fi } ensure_repo_initialized() { # Sync the repo if it's not there. # Mainly used for dev+test, repo should be handled mostly by the user. if [ ! -d "${BASEDIR}/.repo" ]; then echo ".repo missing, initializing..." pushd "${BASEDIR}" > /dev/null repo init -u "${BASEDIR}/meta" --git-lfs repo sync popd > /dev/null fi } clean_kernel_source() { # Reset the kernel source to an unmodified state. # This is mostly necessary since we apply modifications to the source and it's kinda hard to track them. echo "Preparing source..." pushd "${BASEDIR}/kernel/oneplus/sm8750" > /dev/null git reset --hard git clean -fd popd > /dev/null } add_sukisu() { pushd "${BASEDIR}/kernel/oneplus/sm8750" > /dev/null ln -sf "${BASEDIR}/external/SukiSU-Ultra" "KernelSU" ln -sf "${BASEDIR}/external/SukiSU-Ultra/kernel" "drivers/kernelsu" ${BUSYBOX} grep -q "kernelsu" "drivers/Makefile" || ${BUSYBOX} printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "drivers/Makefile" && echo "[+] Modified Makefile." ${BUSYBOX} grep -q "source \"drivers/kernelsu/Kconfig\"" "drivers/Kconfig" || ${BUSYBOX} sed -i "/endmenu/i\source \"drivers/kernelsu/Kconfig\"" "drivers/Kconfig" && echo "[+] Modified Kconfig." popd > /dev/null } add_susfs() { pushd "${BASEDIR}/kernel/oneplus/sm8750" > /dev/null cp "${BASEDIR}/external/susfs4ksu/kernel_patches/fs/"* "fs/" || { echo "Failed to copy fs patches"; exit 1; } cp "${BASEDIR}/external/susfs4ksu/kernel_patches/include/linux/"* "include/linux/" || { echo "Failed to copy linux patches"; exit 1; } # Patch modified so that it works within a constrained environment patch -p1 < "${BASEDIR}/meta/50_add_susfs_in_gki-android15-6.6.patch" || { echo "Failed to patch kernel source for susfs"; exit 1; } popd > /dev/null } configure_kernel() { pushd "${BASEDIR}/kernel/oneplus/sm8750" > /dev/null # Ensure that the output directories exist mkdir -p "${BASEDIR}/out" mkdir -p "${BASEDIR}/work" # Start by cleaning directories and starting with the GKI config make ${MAKEOPTS[@]} mrproper make ${MAKEOPTS[@]} gki_defconfig # Add customizations specific to our device scripts/kconfig/merge_config.sh -m -O "${BASEDIR}/work" \ "${BASEDIR}/work/.config" \ arch/arm64/configs/vendor/sun_perf.config \ arch/arm64/configs/vendor/oplus/sun_perf.config # Add further customizations to keep in line with vanilla echo "CONFIG_DODGE_DTB=y" >> "${BASEDIR}/work/.config" echo "CONFIG_OPLUS_DEVICE_DTBS=y" >> "${BASEDIR}/work/.config" echo "CONFIG_COMPAT=y" >> "${BASEDIR}/work/.config" echo "CONFIG_COMPAT_VDSO=y" >> "${BASEDIR}/work/.config" # Add custom configurations if we're not looking for the stock configuration if [ "${1}" != "stock" ]; then if [ -e "${BASEDIR}/.config" ]; then echo "Adding custom configuration..." scripts/kconfig/merge_config.sh -m -O "${BASEDIR}/work" \ "${BASEDIR}/work/.config" \ "${BASEDIR}/.config" fi fi # Run a final olddefconfig to make sure that the config is complete for non-interactive compile. make ${MAKEOPTS[@]} olddefconfig popd > /dev/null } build_kernel() { pushd "${BASEDIR}/kernel/oneplus/sm8750" > /dev/null make ${MAKEOPTS[@]} \ KCFLAGS="-Wno-error=frame-larger-than=" \ -j"$(${BUSYBOX} nproc)" 2>&1 | ${BUSYBOX} tee "${BASEDIR}/work/build.log" popd > /dev/null } prepare_anykernel() { echo "Preparing AnyKernel3 package..." # Start with a new empty folder rm -rf "${BASEDIR}/work/AnyKernel3" mkdir -p "${BASEDIR}/work/AnyKernel3" # Copy down a fresh revision of AK3 (should exclude dotfiles) cp -ra "${BASEDIR}/external/AnyKernel3/"* "${BASEDIR}/work/AnyKernel3/" # Add in our customized anykernel.sh cp "${BASEDIR}/meta/anykernel.sh" "${BASEDIR}/work/AnyKernel3/anykernel.sh" # Add in arm64 versions of tools from Magisk project cp "${BASEDIR}/prebuilts/magisk/lib/arm64-v8a/libbusybox.so" "${BASEDIR}/work/AnyKernel3/tools/busybox" cp "${BASEDIR}/prebuilts/magisk/lib/arm64-v8a/libmagiskboot.so" "${BASEDIR}/work/AnyKernel3/tools/magiskboot" cp "${BASEDIR}/prebuilts/magisk/lib/arm64-v8a/libmagiskpolicy.so" "${BASEDIR}/work/AnyKernel3/tools/magiskpolicy" # Remove unused tools rm -f "${BASEDIR}/work/AnyKernel3/tools/fec" rm -f "${BASEDIR}/work/AnyKernel3/tools/httools_static" rm -f "${BASEDIR}/work/AnyKernel3/tools/lptools_static" rm -f "${BASEDIR}/work/AnyKernel3/tools/snapshotupdater_static" # Ensure the tools are executable chmod +x "${BASEDIR}/work/AnyKernel3/tools/"* echo "Copying kernel Image..." if [ ! -f "${BASEDIR}/work/arch/arm64/boot/Image" ]; then echo "Error: Kernel build failed, Image not found" exit 1 fi cp "${BASEDIR}/work/arch/arm64/boot/Image" "${BASEDIR}/work/AnyKernel3/" } create_flashable_zip() { echo "Creating flashable zip..." KERNEL_VERSION=$(cat "${BASEDIR}/kernel/oneplus/sm8750/include/config/kernel.release" 2>/dev/null || echo "unknown") OUTPUT_ZIP="${BASEDIR}/out/LiteKernel-${KERNEL_VERSION}-$(date +%Y%m%d-%H%M%S).zip" pushd "${BASEDIR}/work/AnyKernel3" > /dev/null zip -r9 "${OUTPUT_ZIP}" * -x .git README.md .gitignore ./*.zip popd > /dev/null echo "Build complete!" echo "Flashable zip: ${OUTPUT_ZIP}" } run_menuconfig() { set_vars mkdir -p "${BASEDIR}/out" mkdir -p "${BASEDIR}/work" pushd "${BASEDIR}/kernel/oneplus/sm8750" > /dev/null echo "Generating base configuration..." configure_kernel stock cp "$BASEDIR/work/.config" "$BASEDIR/work/.config.base" if [ -e "${BASEDIR}/.config" ]; then echo "Merging existing custom configuration..." scripts/kconfig/merge_config.sh -m -O "${BASEDIR}/work" \ "${BASEDIR}/work/.config" \ "${BASEDIR}/.config" make ${MAKEOPTS[@]} olddefconfig fi make ${MAKEOPTS[@]} menuconfig extract_custom_config popd > /dev/null } extract_custom_config() { echo "Extracting custom configuration differences..." if [ ! -f "${BASEDIR}/work/.config.base" ]; then echo "Warning: No base config found for comparison" return fi > "${BASEDIR}/.config" declare -A base_configs declare -A current_configs while IFS= read -r line; do if [[ "$line" =~ ^CONFIG_([^=]+)= ]]; then config_name="${BASH_REMATCH[1]}" base_configs["$config_name"]="$line" elif [[ "$line" =~ ^#\ (CONFIG_[^\ ]+)\ is\ not\ set ]]; then config_name="${BASH_REMATCH[1]#CONFIG_}" base_configs["$config_name"]="$line" fi done < "${BASEDIR}/work/.config.base" while IFS= read -r line; do if [[ "$line" =~ ^CONFIG_([^=]+)= ]]; then config_name="${BASH_REMATCH[1]}" current_configs["$config_name"]="$line" elif [[ "$line" =~ ^#\ (CONFIG_[^\ ]+)\ is\ not\ set ]]; then config_name="${BASH_REMATCH[1]#CONFIG_}" current_configs["$config_name"]="$line" fi done < "${BASEDIR}/work/.config" for config_name in "${!current_configs[@]}"; do if [[ "${base_configs[$config_name]}" != "${current_configs[$config_name]}" ]]; then echo "${current_configs[$config_name]}" >> "${BASEDIR}/.config" fi done if [ -s "${BASEDIR}/.config" ]; then ${BUSYBOX} sort -o "${BASEDIR}/.config" "${BASEDIR}/.config" echo "" echo "Custom configuration saved to: ${BASEDIR}/.config" echo "Changes from base configuration:" ${BUSYBOX} cat "${BASEDIR}/.config" echo "" else echo "No configuration changes from base detected." rm -f "${BASEDIR}/.config" fi } download_lineageos_boot() { set_vars echo "Fetching latest LineageOS build information for dodge..." BOOT_URL=$(${BUSYBOX} wget -qO- "https://download.lineageos.org/api/v2/devices/dodge/builds" | ${PYTHON} -c 'import sys,json;d=json.load(sys.stdin);boot=[f for f in d[0]["files"] if f["filename"]=="boot.img"][0];print(boot["url"])' 2>/dev/null) if [ -z "$BOOT_URL" ]; then echo "Error: Failed to fetch boot.img URL from LineageOS API" return 1 fi echo "Found boot.img URL: $BOOT_URL" echo "Downloading boot.img..." ${BUSYBOX} wget -O "${BASEDIR}/stock_boot.img" "$BOOT_URL" || { echo "Error: Failed to download boot.img"; return 1; } if [ ! -f "${BASEDIR}/stock_boot.img" ]; then echo "Error: Failed to download boot.img" return 1 fi echo "Boot image saved to: ${BASEDIR}/stock_boot.img" } repack_boot_image() { set_vars if [ ! -f "${BASEDIR}/stock_boot.img" ]; then echo "stock_boot.img not found, attempting to download from LineageOS..." download_lineageos_boot || return 1 fi if [ ! -f "${BASEDIR}/work/arch/arm64/boot/Image" ]; then echo "Error: Kernel Image not found at ${BASEDIR}/work/arch/arm64/boot/Image" echo "Please build the kernel first using: $(basename "$0") build" return 1 fi MAGISKBOOT="${BASEDIR}/prebuilts/magisk/lib/x86_64/libmagiskboot.so" chmod +x "${MAGISKBOOT}" echo "Repacking boot image..." rm -rf "${BASEDIR}/out/repack" mkdir -p "${BASEDIR}/out/repack" pushd "${BASEDIR}/out/repack" > /dev/null ${MAGISKBOOT} unpack "${BASEDIR}/stock_boot.img" cp "${BASEDIR}/work/arch/arm64/boot/Image" kernel ${MAGISKBOOT} repack "${BASEDIR}/stock_boot.img" "${BASEDIR}/out/new_boot.img" popd > /dev/null echo "" echo "Boot image repacked successfully!" echo "Output: ${BASEDIR}/out/new_boot.img" } build() { set -e set_vars ensure_repo_initialized clean_kernel_source add_sukisu add_susfs configure_kernel build_kernel prepare_anykernel create_flashable_zip } main() { # Start by checking if all necessary tools are installed. check_tools local command="${1:-build}" case "$command" in build) build ;; menuconfig) run_menuconfig ;; repack) repack_boot_image ;; download) download_lineageos_boot ;; help|--help|-h) usage ;; *) echo "Error: Unknown command '$command'" echo "" usage ;; esac } main "$@"