Rockchip RK3588 - Rockchip Linux SDK腳本分析

----------------------------------------------------------------------------------------------------------------------------

開發板 :ArmSoM-Sige7開發板eMMC64GBLPDDR48GB
顯示屏 :15.6英寸HDMI接口顯示屏u-boot2017.09linux5.10
----------------------------------------------------------------------------------------------------------------------------

在《Rockchip RK3588 - Rockchip Linux SDK編譯》我們SDK的編譯流程以及固件升級相關的內容,本節將會對編譯腳本進行深入的分析。

一、build.sh分析

Rockchip Linux SDK編譯命令是由build.sh腳本實現的,其入口函數為main函數。

1.1 main函數

由于main函數代碼內容較長,我們只對重點內容進行分析:

點擊查看代碼
main()
{[ -z "$DEBUG" ] || set -xtrap 'err_handler' ERRset -eE# Save intial envionmentsunset INITIAL_SESSIONINITIAL_ENV=$(mktemp -u)if [ -z "$RK_SESSION" ]; thenINITIAL_SESSION=1env > "$INITIAL_ENV"fiexport LC_ALL=Cexport SCRIPTS_DIR="$(dirname "$(realpath "$BASH_SOURCE")")"export COMMON_DIR="$(realpath "$SCRIPTS_DIR/..")"export SDK_DIR="$(realpath "$COMMON_DIR/../../..")"export DEVICE_DIR="$SDK_DIR/device/rockchip"export CHIPS_DIR="$DEVICE_DIR/.chips"export CHIP_DIR="$DEVICE_DIR/.chip"export RK_DATA_DIR="$COMMON_DIR/data"export RK_TOOL_DIR="$COMMON_DIR/tools"export RK_IMAGE_DIR="$COMMON_DIR/images"export RK_KBUILD_DIR="$COMMON_DIR/linux-kbuild"export RK_CONFIG_IN="$COMMON_DIR/configs/Config.in"export RK_BUILD_HOOK_DIR="$COMMON_DIR/build-hooks"export BUILD_HELPER="$RK_BUILD_HOOK_DIR/build-helper"export RK_POST_HOOK_DIR="$COMMON_DIR/post-hooks"export POST_HELPER="$RK_POST_HOOK_DIR/post-helper"export PARTITION_HELPER="$SCRIPTS_DIR/partition-helper"export RK_SESSION="${RK_SESSION:-$(date +%F_%H-%M-%S)}"export RK_OUTDIR="$SDK_DIR/output"export RK_SESSION_DIR="$RK_OUTDIR/sessions"export RK_LOG_BASE_DIR="$RK_OUTDIR/log"export RK_LOG_DIR="$RK_SESSION_DIR/$RK_SESSION"export RK_INITIAL_ENV="$RK_LOG_DIR/initial.env"export RK_CUSTOM_ENV="$RK_LOG_DIR/custom.env"export RK_FINAL_ENV="$RK_LOG_DIR/final.env"export RK_ROCKDEV_DIR="$SDK_DIR/rockdev"export RK_FIRMWARE_DIR="$RK_OUTDIR/firmware"export RK_SECURITY_FIRMWARE_DIR="$RK_OUTDIR/security-firmware"export RK_CONFIG="$RK_OUTDIR/.config"export RK_DEFCONFIG_LINK="$RK_OUTDIR/defconfig"# For Makefilecase "$@" inmake-targets)# Chip targetsls "$CHIPS_DIR";&make-usage)run_build_hooks "$@"rm -f "$INITIAL_ENV"exit 0 ;;esac# Log SDK informationMANIFEST="$SDK_DIR/.repo/manifest.xml"if [ -e "$MANIFEST" ]; thenif [ ! -L "$MANIFEST" ]; thenMANIFEST="$SDK_DIR/.repo/manifests/$(grep -o "[^\"]*\.xml" "$MANIFEST")"fiTAG="$(grep -o "linux-.*-gen-rkr[^.\"]*" "$MANIFEST" | \head -n 1 || true)"MANIFEST="$(basename "$(realpath "$MANIFEST")")"echoecho -e "\e[35m############### Rockchip Linux SDK ###############\e[0m"echoecho -e "\e[35mManifest: $MANIFEST\e[0m"if [ "$TAG" ]; thenecho -e "\e[35mVersion: $TAG\e[0m"fiechofi# Prepare firmware dirsmkdir -p "$RK_FIRMWARE_DIR" "$RK_SECURITY_FIRMWARE_DIR"cd "$SDK_DIR"[ -f README.md ] || ln -rsf "$COMMON_DIR/README.md" .[ -d common ] || ln -rsf "$COMMON_DIR" .# TODO: Remove it in the repo manifest.xmlrm -f envsetup.shOPTIONS=${@:-allsave}# Special handle for chip and defconfig# e.g. ./build.sh rk3588:rockchip_defconfigfor opt in $OPTIONS; doif [ -d "$CHIPS_DIR/${opt%%:*}" ]; thenOPTIONS=$(echo "$OPTIONS" | xargs -n 1 | \sed "s/^$opt$/chip:$opt/" | xargs)elif echo "$opt" | grep -q "^[0-9a-z_]*_defconfig$"; thenOPTIONS=$(echo "$OPTIONS" | xargs -n 1 | \sed "s/^$opt$/defconfig:$opt/" | xargs)fidone# Options checkingCMDS="$(run_build_hooks support-cmds all | xargs)"for opt in $OPTIONS; docase "$opt" inhelp | h | -h | --help | usage | \?) usage ;;clean:*)# Check cleanup modulesfor m in $(echo ${opt#clean:} | tr ':' ' '); dogrep -wq clean_hook \"$SCRIPTS_DIR/mk-$m.sh" \2>/dev/null || usagedone;&shell | cleanall)# Check single optionsif [ "$opt" = "$OPTIONS" ]; thenbreakfiecho "ERROR: $opt cannot combine with other options!";;post-rootfs)if [ "$opt" = "$1" -a -d "$2" ]; then# Hide other args from build stagesOPTIONS=$optbreakfiecho "ERROR: $opt should be the first option followed by rootfs dir!";;*)# Make sure that all options are handledif option_check "$CMDS" $opt; thencontinuefiecho "ERROR: Unhandled option: $opt";;esacusagedone# Prepare log dirsif [ ! -d "$RK_LOG_DIR" ]; thenrm -rf "$RK_LOG_BASE_DIR" "$RK_LOG_DIR" "$RK_SESSION_DIR/latest"mkdir -p "$RK_LOG_DIR"ln -rsf "$RK_SESSION_DIR" "$RK_LOG_BASE_DIR"ln -rsf "$RK_LOG_DIR" "$RK_SESSION_DIR/latest"echo -e "\e[33mLog saved at $RK_LOG_DIR\e[0m"echofi# Drop old logscd "$RK_LOG_BASE_DIR"rm -rf $(ls -t | sed '1,10d')cd "$SDK_DIR"# Save initial envionmentsif [ "$INITIAL_SESSION" ]; thenrm -f "$RK_INITIAL_ENV"mv "$INITIAL_ENV" "$RK_INITIAL_ENV"ln -rsf "$RK_INITIAL_ENV" "$RK_OUTDIR/"fi# Init stage (preparing SDK configs, etc.)run_build_hooks init $OPTIONSrm -f "$RK_OUTDIR/.tmpconfig*"# No need to go furtherCMDS="$(run_build_hooks support-cmds pre-build build \post-build | xargs) cleanall clean post-rootfs"option_check "$CMDS" $OPTIONS || return 0# Force exporting config environmentsset -a# Load config environmentssource "$RK_CONFIG"cp "$RK_CONFIG" "$RK_LOG_DIR"if [ -z "$INITIAL_SESSION" ]; then# Inherit session environmentssed -n 's/^\(RK_.*=\)\(.*\)/\1"\2"/p' "$RK_FINAL_ENV" > \"$INITIAL_ENV"source "$INITIAL_ENV"rm -f "$INITIAL_ENV"else# Detect and save custom environments# Find custom environmentsrm -f "$RK_CUSTOM_ENV"for cfg in $(grep "^RK_" "$RK_INITIAL_ENV" || true); doenv | grep -q "^${cfg//\"/}$" || \echo "$cfg" >> "$RK_CUSTOM_ENV"done# Allow custom environments overridingif [ -e "$RK_CUSTOM_ENV" ]; thenln -rsf "$RK_CUSTOM_ENV" "$RK_OUTDIR/"echo -e "\e[31mWARN: Found custom environments: \e[0m"cat "$RK_CUSTOM_ENV"echo -e "\e[31mAssuming that is expected, please clear them if otherwise.\e[0m"read -t 10 -p "Press enter to continue."source "$RK_CUSTOM_ENV"if grep -q "^RK_KERNEL_VERSION=" "$RK_CUSTOM_ENV"; thenecho -e "\e[31mCustom RK_KERNEL_VERSION ignored!\e[0m"load_config RK_KERNEL_VERSIONfiif grep -q "^RK_ROOTFS_SYSTEM=" "$RK_CUSTOM_ENV"; thenecho -e "\e[31mCustom RK_ROOTFS_SYSTEM ignored!\e[0m"load_config RK_ROOTFS_SYSTEMfififisource "$PARTITION_HELPER"rk_partition_initset +aexport PYTHON3=/usr/bin/python3export RK_KERNEL_VERSION_REAL=$(kernel_version_real)# Handle special commandscase "$OPTIONS" incleanall)run_build_hooks cleanrm -rf "$RK_OUTDIR" "$SDK_DIR/rockdev"finish_build cleanallexit 0 ;;clean:*)MODULES="$(echo ${OPTIONS#clean:} | tr ':' ' ')"for m in $MODULES; do"$SCRIPTS_DIR/mk-$m.sh" cleandonefinish_build clean - $MODULESexit 0 ;;post-rootfs)shiftrun_post_hooks $@finish_build post-rootfsexit 0 ;;esac# Save final environmentsrm -f "$RK_FINAL_ENV"env > "$RK_FINAL_ENV"ln -rsf "$RK_FINAL_ENV" "$RK_OUTDIR/"# Log configsechoecho "=========================================="echo "          Final configs"echo "=========================================="env | grep -E "^RK_.*=.+" | grep -vE "PARTITION_[0-9]" | \grep -vE "=\"\"$|_DEFAULT=y" | \grep -vE "^RK_CONFIG|_BASE_CFG=|_LINK=|DIR=|_ENV=|_NAME=" | sortecho# Pre-build stage (submodule configuring, etc.)run_build_hooks pre-build $OPTIONS# No need to go furtherCMDS="$(run_build_hooks support-cmds build post-build | xargs)"option_check "$CMDS" $OPTIONS || return 0# Build stage (building, etc.)run_build_hooks build $OPTIONS# No need to go furtherCMDS="$(run_build_hooks support-cmds post-build | xargs)"option_check "$CMDS" $OPTIONS || return 0# Post-build stage (firmware packing, etc.)run_build_hooks post-build $OPTIONS
}
1.1.1 提示模式和錯誤處理

調試模式設置:首先通過判斷是否設置了環境變量DEBUG,決定是否啟用調試模式。如果 DEBUG 被設置為非空值,則運行 set -x 開啟調試模式,這樣腳本執行時會顯示每一行命令的執行情況。

錯誤處理:使用 trapERR 捕獲腳本中的錯誤。一旦發生錯誤,將調用 err_handler 函數;同時,set -eE 確保如果任何命令失敗,腳本會立即退出,并且支持 ERR 跟蹤。

保存初始環境變量:接著嘗試保存當前的環境變量到一個臨時文件中。首先,它解除定義了 INITIAL_SESSION 變量(如果存在),然后使用 mktemp -u 生成一個唯一的臨時文件路徑。如果 RK_SESSION 變量未設置,則認為這是一個新的會話,并將所有當前環境變量保存到生成的臨時文件中。

1.1.2 設置環境變量

接著是設置一系列環境變,這些變量定義了一些重要的目錄路徑,通常在腳本執行過程中會用到這些路徑。

export LC_ALL=Cexport SCRIPTS_DIR="$(dirname "$(realpath "$BASH_SOURCE")")"
export COMMON_DIR="$(realpath "$SCRIPTS_DIR/..")"
......

realpath "$BASH_SOURCE" 獲取當前腳本的絕對路徑,由于build.sh指向了device/rockchip/common/scripts/build.sh;所以:

SCRIPTS_DIR=<SDK>/device/rockchip/common/scripts
COMMON_DIR=<SDK>/device/rockchip/common
SDK_DIR=<SDK>
DEVICE_DIR=<SDK>/device/rockchip
CHIPS_DIR=<SDK>/device/rockchip/.chips
CHIP_DIR=<SDK>/device/rockchip/.chip
RK_DATA_DIR=<SDK>/device/rockchip/common/data
RK_TOOL_DIR=<SDK>/device/rockchip/common/tools
RK_IMAGE_DIR=<SDK>/device/rockchip/common/images
RK_KBUILD_DIR=<SDK>/device/rockchip/common/linux-kbuild
RK_CONFIG_IN=<SDK>/device/rockchip/common/configs/Config.in
RK_BUILD_HOOK_DIR=<SDK>/device/rockchip/common/build-hooks
BUILD_HELPER=<SDK>/device/rockchip/common/build-hooks/build-helper
RK_POST_HOOK_DIR=<SDK>/device/rockchip/common/post-hooks
POST_HELPER=<SDK>/device/rockchip/common/post-hooks/post-helper
PARTITION_HELPER=<SDK>/device/rockchip/common/scripts/partition-helper
RK_SESSION=2024-07-08_21-52-58
RK_OUTDIR=<SDK>/output
RK_SESSION_DIR=<SDK>output/sessions
RK_LOG_BASE_DIR=<SDK>/output/log
RK_LOG_DIR=<SDK>/output/sessions/2024-07-08_21-52-58
RK_INITIAL_ENV=<SDK>/output/sessions/2024-07-08_21-52-58/initial.env
RK_CUSTOM_ENV=<SDK>/output/sessions/2024-07-08_21-52-58/custom.env
RK_FINAL_ENV=<SDK>/output/sessions/2024-07-08_21-52-58/final.env
RK_ROCKDEV_DIR=<SDK>/rockdev
RK_FIRMWARE_DIR=<SDK>/output/firmware
RK_SECURITY_FIRMWARE_DIR=<SDK>/output/security-firmware
RK_CONFIG=<SDK>/output/.config
RK_DEFCONFIG_LINK=<SDK>/output/defconfig
1.1.3 選項make-targets/make-usage

接著就是對選項make-targetsmake-usage的支持,實際上也是輸出幫助信息。

1.1.4 選項chip/defconfig

接著就是對如下選項的支持:

  • chip[:<chip>[:<config>]] <chip>可選,表示SoC,比如rk3588<config>可選,表示板級配置,比如rockchip_defconfig
  • defconfig[:<config>]:<config>可選,表示板級配置,比如rockchip_defconfig

(1) 如果有參數傳遞給腳本,則OPTIONS被設置為這些參數的組合;如果沒有參數傳遞,則OPTIONS被設置為 allsave

(2) 接著對$OPTIONS變量進行特殊處理,用于處理 chipdefconfig的情況。它會檢查每個參數,并根據特定的條件對其進行轉換。

首先:

if [ -d "$CHIPS_DIR/${opt%%:*}" ]; thenOPTIONS=$(echo "$OPTIONS" | xargs -n 1 | \sed "s/^$opt$/chip:$opt/" | xargs)

檢查是否存在以$CHIPS_DIR開頭、后面跟著$opt變量:分隔之前的字符串作為目錄名, 如果滿足;修改 $OPTIONS 變量,替換內容$optchip:$opt

  • 比如./builsh rk3588:rockchip_defconfigopt=rk3588:rockchip_defconfig,處理后OPTIONS=chip:rk3588:rockchip_defconfig

接著:

elif echo "$opt" | grep -q "^[0-9a-z_]*_defconfig$"; thenOPTIONS=$(echo "$OPTIONS" | xargs -n 1 | \sed "s/^$opt$/defconfig:$opt/" | xargs)
fi

如果是以_defconfig結尾命名,修改 $OPTIONS 變量,替換內容$optdefconfig:$opt

  • 比如./builsh rockchip_defconfigopt=rockchip_defconfig,處理后OPTIONS=defconfig:rockchip_defconfig
1.1.5 選項檢查

首先是獲取支持的所有命令:

CMDS="$(run_build_hooks support-cmds all | xargs)"

xargs 將會把 run_build_hooks support-cmds all 的輸出(echo函數的輸出)捕獲,并通過管道傳遞給 xargs

由于run_build_hooks函數會依次執行<SDK>/device/rockchip/common/build-hooks下的*.sh腳本,因此CMDS存儲的就是這些腳本執行的輸出,具體分析參考后文《run_build_hooks》。

執行完畢CMDS中存儲build.sh支持的所有命令;

CMDS='chip defconfig lunch .*_defconfig olddefconfig savedefconfig menuconfig config shell print-parts mod-parts edit-parts new-parts insert-part del-part move-part rename-part resize-part kernel-config kernel-make kmake kernel modules linux-headers wifibt rtos buildroot debian yocto buildroot-config buildroot-make bmake rootfs buildroot debian yocto recovery pcba security_check createkeys security_ramboot security_uboot security_boot security_recovery security_rootfs loader uboot uefi firmware edit-package-file edit-ota-package-file edit-package-file edit-ota-package-file updateimg otapackage all allsave save'

接著是一段選項檢查的代碼;

for opt in $OPTIONS; docase "$opt" inhelp | h | -h | --help | usage | \?) usage ;;clean:*)# Check cleanup modulesfor m in $(echo ${opt#clean:} | tr ':' ' '); dogrep -wq clean_hook \"$SCRIPTS_DIR/mk-$m.sh" \2>/dev/null || usagedone;&shell | cleanall)# Check single optionsif [ "$opt" = "$OPTIONS" ]; thenbreakfiecho "ERROR: $opt cannot combine with other options!";;post-rootfs)if [ "$opt" = "$1" -a -d "$2" ]; then# Hide other args from build stagesOPTIONS=$optbreakfiecho "ERROR: $opt should be the first option followed by rootfs dir!";;*)# Make sure that all options are handledif option_check "$CMDS" $opt; thencontinuefiecho "ERROR: Unhandled option: $opt";;esacusage
done

根據傳入的選項($OPTIONS)進行判斷和處理,這里遍歷選項依次執行:

  • 當選項滿足help | h | -h | --help | usage | \?,執行usage函數。比如我們前文執行 ./build.sh help,就是則調用usage函數來顯示幫助信息;
  • 如果選項以 clean: 開頭,則驗證驗證相應的清理模塊是否存在,比如./build.sh clean:kernel,則判斷"device/rockchip/common/scripts/mk-kernel.sh/mk-kernel.sh中是否包含clean_hook方法;后續會調用mk-kernel.sh clean執行清理工作;
  • 如果選項是shellcleanall,執行函數run_build_hooks clean,該函數則會遍歷device/rockchip/common/scriptsshell腳本文件,并傳入clean參數函數依次執行;
  • 如果選項是post-rootfs,并且滿足上面的條件,則設置OPTIONS=$opt
  • 如果不滿足以上條件,則調用option_check函數檢查給定的命令選項是否在指定的命令列表中,如果匹配失敗,則輸出錯誤信息,并調用usage函數。
1.1.6 準備日志目錄

接著是創建編譯日志目錄:

# Prepare log dirs
if [ ! -d "$RK_LOG_DIR" ]; thenrm -rf "$RK_LOG_BASE_DIR" "$RK_LOG_DIR" "$RK_SESSION_DIR/latest"mkdir -p "$RK_LOG_DIR"ln -rsf "$RK_SESSION_DIR" "$RK_LOG_BASE_DIR"ln -rsf "$RK_LOG_DIR" "$RK_SESSION_DIR/latest"echo -e "\e[33mLog saved at $RK_LOG_DIR\e[0m"echo
fi

首先判斷日志目錄$RK_LOG_DIR是否存在,如果該目錄不存在,則執行一系列操作來創建目錄結構并設置符號鏈接,將 $RK_LOG_DIR符號鏈接到$RK_SESSION_DIR/latest,確保最新日志目錄符號鏈接始終指向最新的日志目錄。

1.1.7 清理舊日志

接著是清理日志目錄,只保留最新的10次編譯的日志記錄:

# Drop old logs
cd "$RK_LOG_BASE_DIR"
rm -rf $(ls -t | sed '1,10d')
cd "$SDK_DIR"

跳轉到<SDK>/output/log目錄,把除了最新的10個文件或目錄外的所有內容傳遞給rm -rf命令進行刪除操作。

1.1.8 保存初始化環境變量

接著是保存初始化環境變量;

# Save initial envionments
if [ "$INITIAL_SESSION" ]; thenrm -f "$RK_INITIAL_ENV"mv "$INITIAL_ENV" "$RK_INITIAL_ENV"ln -rsf "$RK_INITIAL_ENV" "$RK_OUTDIR/"
fi

由于前文設置INITIAL_ENV=$(mktemp-u),即使用mktemp -u生成一個唯一的臨時文件路徑,比如/tmp/tmp.ag1ffjvNMG

因此該段腳本:

  • 它會先刪除舊的<SDK>/output/sessions/initial.env文件;
  • 然后將臨時文件移動并重命名為<SDK>/output/sessions/$RK_SESSION/initial.env
  • 最后在指定的輸出目錄<SDK>/output下創建一個指向<SDK>/output/sessions/$RK_SESSION/initial.env的符號鏈接。
1.1.9 初始化階段

腳本進入初始化階段,用于準備SDK配置等工作;

# Init stage (preparing SDK configs, etc.)
run_build_hooks init $OPTIONS
rm -f "$RK_OUTDIR/.tmpconfig*"

這里再次調用run_build_hooks函數,入參為init $OPTIONS,具體分析參考后文《run_build_hooks》。

1.1.10 選項檢查

接下來就是判斷我們傳入的選項是否是build.sh pre-build build post-build階段中定義的命令。

# No need to go further
CMDS="$(run_build_hooks support-cmds pre-build build \post-build | xargs) cleanall clean post-rootfs"
option_check "$CMDS" $OPTIONS || return 0

xargs 將會把 run_build_hooks support-cmds pre-build build post-build 的輸出(echo函數的輸出)捕獲,并通過管道傳遞給 xargs

由于run_build_hooks函數會依次執行<SDK>/device/rockchip/common/build-hooks下的sh腳本,因此CMDS存儲的就是這些腳本執行的輸出。

執行完畢CMDS中存儲build.shpre-build build post-build階段中支持的命令;

CMDS='shell print-parts mod-parts edit-parts new-parts insert-part del-part move-part rename-part resize-part kernel-config kernel-make kmake kernel modules linux-headers wifibt rtos buildroot-config buildroot-make bmake rootfs buildroot debian yocto recovery pcba security_check createkeys security_ramboot security_uboot security_boot security_recovery security_rootfs loader uboot uefi firmware edit-package-file edit-ota-package-file updateimg otapackage all allsave save cleanall clean post-rootfs'

接著調用option_check檢查變量CMDS中的命令是否包含在$OPTIONS指定的選項中。

  • 如果找到任何一個在任何一個選項中有匹配,則函數返回0,表示命令匹配成功;
  • 如果所有的命令都沒有在任何一個選項中找到匹配,則函數最終返回1,表示命令匹配失敗。

如果option_check函數返回非0值(即失敗),則執行return 0,表示退出當前函數。

1.1.11 導出config environments
# Force exporting config environments
set -a# Load config environments
source "$RK_CONFIG"
cp "$RK_CONFIG" "$RK_LOG_DIR"if [ -z "$INITIAL_SESSION" ]; then# Inherit session environmentssed -n 's/^\(RK_.*=\)\(.*\)/\1"\2"/p' "$RK_FINAL_ENV" > \"$INITIAL_ENV"source "$INITIAL_ENV"rm -f "$INITIAL_ENV"
else# Detect and save custom environments# Find custom environmentsrm -f "$RK_CUSTOM_ENV"for cfg in $(grep "^RK_" "$RK_INITIAL_ENV" || true); doenv | grep -q "^${cfg//\"/}$" || \echo "$cfg" >> "$RK_CUSTOM_ENV"done# Allow custom environments overridingif [ -e "$RK_CUSTOM_ENV" ]; thenln -rsf "$RK_CUSTOM_ENV" "$RK_OUTDIR/"echo -e "\e[31mWARN: Found custom environments: \e[0m"cat "$RK_CUSTOM_ENV"echo -e "\e[31mAssuming that is expected, please clear them if otherwise.\e[0m"read -t 10 -p "Press enter to continue."source "$RK_CUSTOM_ENV"if grep -q "^RK_KERNEL_VERSION=" "$RK_CUSTOM_ENV"; thenecho -e "\e[31mCustom RK_KERNEL_VERSION ignored!\e[0m"load_config RK_KERNEL_VERSIONfiif grep -q "^RK_ROOTFS_SYSTEM=" "$RK_CUSTOM_ENV"; thenecho -e "\e[31mCustom RK_ROOTFS_SYSTEM ignored!\e[0m"load_config RK_ROOTFS_SYSTEMfifi
fisource "$PARTITION_HELPER"
rk_partition_initset +a
1.1.12 選項claenall/clean:*/post-rootfs
export PYTHON3=/usr/bin/python3
export RK_KERNEL_VERSION_REAL=$(kernel_version_real)# Handle special commands
case "$OPTIONS" incleanall)run_build_hooks cleanrm -rf "$RK_OUTDIR" "$SDK_DIR/rockdev"finish_build cleanallexit 0 ;;clean:*)MODULES="$(echo ${OPTIONS#clean:} | tr ':' ' ')"for m in $MODULES; do"$SCRIPTS_DIR/mk-$m.sh" cleandonefinish_build clean - $MODULESexit 0 ;;post-rootfs)shiftrun_post_hooks $@finish_build post-rootfsexit 0 ;;
esac
1.1.13 保存final環境變量
# Save final environments
rm -f "$RK_FINAL_ENV"
env > "$RK_FINAL_ENV"
ln -rsf "$RK_FINAL_ENV" "$RK_OUTDIR/"
1.1.14 預編譯/編譯/編譯后
# Log configs
echo
echo "=========================================="
echo "          Final configs"
echo "=========================================="
env | grep -E "^RK_.*=.+" | grep -vE "PARTITION_[0-9]" | \grep -vE "=\"\"$|_DEFAULT=y" | \grep -vE "^RK_CONFIG|_BASE_CFG=|_LINK=|DIR=|_ENV=|_NAME=" | sort
echo# Pre-build stage (submodule configuring, etc.)
run_build_hooks pre-build $OPTIONS# No need to go further
CMDS="$(run_build_hooks support-cmds build post-build | xargs)"
option_check "$CMDS" $OPTIONS || return 0# Build stage (building, etc.)
run_build_hooks build $OPTIONS# No need to go further
CMDS="$(run_build_hooks support-cmds post-build | xargs)"
option_check "$CMDS" $OPTIONS || return 0# Post-build stage (firmware packing, etc.)
run_build_hooks post-build $OPTIONS
1.2 option_check

option_check函數檢查給定的命令選項是否在指定的命令列表中。

option_check()
{# 將第一個參數$1賦值給變量CMDS,這個參數是一組命令列表,用空格分隔CMDS="$1"# 將參數列表向左移動一位,去掉了第一個參數(即CMDS),剩下的參數存儲在$@中shift# 嵌套循環檢查選項和命令for opt in $@; do     # 遍歷傳遞給函數的所有剩余參數(除了第一個參數CMDS)for cmd in $CMDS; do    # 再嵌套一個循環,遍歷存儲在CMDS變量中的命令列表# NOTE: There might be patterns in commands 檢查命令是否匹配選項,如果grep 命令沒有匹配到,則繼續下一次循>環echo "${opt%%:*}" | grep -q "^$cmd$" || continue# 如果匹配成功,則返回狀態碼 0,表示找到了匹配的命令選項return 0donedonereturn 1
}

比如我們給函數傳入:

commands="run jump swim"
option_check "$commands" run swim sleep

在這個示例中:

  • $commands被傳遞給CMDS變量,即CMDS="run jump swim;
  • run swim sleep是要檢查的選項,即option1=runoption2=swimoption3=sleep
    函數會逐個檢查每個選項是否在命令列表CMDS中,如果找到任意匹配的命令,則返回狀態碼0;否則返回狀態碼1。
1.3 run_hooks

run_hooks在指定目錄中查找并執行所有以 .sh 結尾的腳本文件。如果執行任何一個腳本失敗,它將調用錯誤處理函數 err_handler 處理錯誤,并以失敗的返回碼退出整個腳本。

run_hooks()
{# 將第一個參數$1賦值給變量DIRDIR="$1"# 將參數列表向左移動一位,去掉了第一個參數(即DIR),剩下的參數存儲在$@中shift# 遍歷目錄for dir in "$CHIP_DIR/$(basename "$DIR")/" "$DIR"; do# 當前的$dir是否是一個存在的目錄。如果不是,則繼續到下一個循環迭代[ -d "$dir" ] || continue# 在$dir目錄下(不深入子目錄,-maxdepth 1)查找所有以.sh結尾的文件for hook in $(find "$dir" -maxdepth 1 -name "*.sh" | sort); do"$hook" $@ && continue   # *.sh退出狀態碼為0則執行continueHOOK_RET=$?err_handler $HOOK_RET "${FUNCNAME[0]} $*" "$hook $*"exit $HOOK_RETdonedone
}

比如我們給函數傳入:

RK_BUILD_HOOK_DIR=<SDK>/device/rockchip/common/build-hooks
run_hooks "$RK_BUILD_HOOK_DIR" support-cmds all

在這個示例中:

  • 將遍歷目錄<SDK>/device/rockchip/.chip/build-hooks<SDK>/device/rockchip/common/build-hooks

  • 由于<SDK>/device/rockchip/.chip/build-hooks不是目錄將跳過;

  • 接著在<SDK>/device/rockchip/common/build-hooks 目錄下(不深入子目錄,-maxdepth 1)查找所有以.sh 結尾的文件;依次執行

    • 執行找到的每一個腳本文件,傳遞參數support-cmds all
    • 如果執行成功(exit 0),則繼續下一個鉤子腳本的執行;
    • 如果執行失敗(exit 非0),調用err_handler 函數處理錯誤,傳遞錯誤碼、當前函數名以及傳遞給當前函數的所有參數,以HOOK_RET的值退出腳本,終止整個構建過程。

我們查看<SDK>/device/rockchip/common/build-hooks目錄下的sh腳本;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll device/rockchip/common/build-hooks/
lrwxrwxrwx  1 root root   23  6月  9 12:58 00-config.sh -> ../scripts/mk-config.sh*
lrwxrwxrwx  1 root root   22  6月  9 12:58 00-shell.sh -> ../scripts/mk-shell.sh*
lrwxrwxrwx  1 root root   27  6月  9 12:58 05-partitions.sh -> ../scripts/mk-partitions.sh*
lrwxrwxrwx  1 root root   23  6月  9 12:58 10-kernel.sh -> ../scripts/mk-kernel.sh*
lrwxrwxrwx  1 root root   23  6月  9 12:58 20-wifibt.sh -> ../scripts/mk-wifibt.sh*
lrwxrwxrwx  1 root root   21  6月  9 12:58 25-rtos.sh -> ../scripts/mk-rtos.sh*
lrwxrwxrwx  1 root root   23  6月  9 12:58 30-rootfs.sh -> ../scripts/mk-rootfs.sh*
lrwxrwxrwx  1 root root   25  6月  9 12:58 40-recovery.sh -> ../scripts/mk-recovery.sh*
lrwxrwxrwx  1 root root   21  6月  9 12:58 50-pcba.sh -> ../scripts/mk-pcba.sh*
lrwxrwxrwx  1 root root   25  6月  9 12:58 60-security.sh -> ../scripts/mk-security.sh*
lrwxrwxrwx  1 root root   23  6月  9 12:58 70-loader.sh -> ../scripts/mk-loader.sh*
lrwxrwxrwx  1 root root   25  6月  9 12:58 80-firmware.sh -> ../scripts/mk-firmware.sh*
lrwxrwxrwx  1 root root   26  6月  9 12:58 90-updateimg.sh -> ../scripts/mk-updateimg.sh*
lrwxrwxrwx  1 root root   20  6月  9 12:58 99-all.sh -> ../scripts/mk-all.sh*
lrwxrwxrwx  1 root root   23  6月  9 12:58 build-helper -> ../scripts/build-helper
-rwxr-xr-x  1 root root 4210  6月  9 12:58 example.sh.in*

其中,比較重要的有:

  • 00-config.sh:板載配置相關,支持的編譯選項有chipdefconfigmenuconfig等;
  • 05-partitions.sh:分區配置相關,支持的編譯選項有:print-partsmod-parts edit-parts等;
  • 10-kernel.sh:內核編譯相關,支持的編譯選項有:kernelmoduleslinux-headers等;
  • 20-wifibt.shwifibt編譯相關;
  • 30-rootfs.sh:根文件系統編譯相關,支持的編譯選項有:buildrootyoctodebian等;
  • 40-recovery.shrecovery編譯相關;
  • 70-loader.shuboot編譯相關,支持的編譯選項有:loader[:cmds]uboot[:cmds] uefi[:cmds] 等;
  • 80-firmware.sh:分區鏡像打包相關;
  • 90-updateimg.sh:統一固件打包相關;
  • 99-all.sh :所有,支持的編譯選項有:allsave等;

在該示例中將會按順序依次執行*.sh腳本,并傳遞參數support-cmds all,有關這些*.sh腳本的執行我們在后面介紹。

1.4 run_build_hooks

run_build_hooks函數作用是運行一些構建過程中的鉤子(hooks),并且記錄執行日志。

run_build_hooks()
{# Don't log these hookscase "$1" ininit | pre-build | make-* | usage | support-cmds)run_hooks "$RK_BUILD_HOOK_DIR" $@ || truereturn 0;;esac# 設置日志文件LOG_FILE="$(start_log "$1")"# 記錄運行日志echo -e "# run hook: $@\n" >> "$LOG_FILE"# 運行鉤子并記錄輸出run_hooks "$RK_BUILD_HOOK_DIR" $@ 2>&1 | tee -a "$LOG_FILE"# 處理鉤子執行結果HOOK_RET=${PIPESTATUS[0]}if [ $HOOK_RET -ne 0 ]; thenerr_handler $HOOK_RET "${FUNCNAME[0]} $*" "$@"exit $HOOK_RETfi
}

函數執行流程如下:

  • 根據傳遞給函數的第一個參數$1進行匹配,如果$1匹配到initpre-build、以make-開頭的任意字符串、usage或者 support-cmds中的一個;

    • 調用run_hooks函數,并傳遞參數<SDK>/device/rockchip/common/build-hooks$@(所有傳遞給當前函數的參數);
    • || true 確保即使run_hooks函數失敗,也不會中斷執行;
    • 返回狀態碼 0,表示成功執行;
  • 設置日志文件:LOG_FILE被設置為一個日志文件的路徑,使用start_log函數生成基于傳入參數$1的日志文件名;

  • 記錄運行日志:將# run hook: $@打印到$LOG_FILE中;

  • 運行鉤子并記錄輸出:

    • 再次調用run_hooks函數,傳遞<SDK>/device/rockchip/common/build-hooks$@,標準錯誤(2)輸出到(>)到標準輸出(&1);
    • 將標準錯誤輸出重定向到標準輸出,這樣可以確保錯誤信息也被tee命令捕獲到;
    • tee -a將管道中的輸出同時輸出到標準輸出和追加到$LOG_FILE文件中;
  • 處理鉤子執行結果:將管道中上一個命令的退出狀態保存在HOOK_RET變量中,PIPESTATUS[0]Bash 內置的數組,包含了最近一個管道中命令的退出狀態;

    • 如果HOOK_RET不等于 0,則表示有錯誤發生;
    • 調用err_handler 數處理錯誤,傳遞錯誤碼、當前函數名以及傳遞給當前函數的所有參數;
    • HOOK_RET的值退出腳本,終止整個構建過程。

在編譯過程中,main函數多次調用run_build_hooks 函數的,其執行順序依次為;

  • run_build_hooks support-cmds all
  • run_build_hooks init recovery
  • run_build_hooks support-cmds pre-build build post-build
  • run_build_hooks pre-build recovery
  • run_build_hooks support-cmds build post-build
  • run_build_hooks build recovery
  • run_build_hooks support-cmds post-build
  • run_build_hooks post-buildrecovery

二、build-helper

build-helper從文件名字不難猜測出來這是一個編譯輔助腳本,其位于<SDK>/device/rockchip/common/build-hooks目錄下,提供了若干個輔助函數。

2. 1 腳本入口

build-helper腳本會被<SDK>/device/rockchip/common/build-hooks下的所有*.sh腳本調用,并傳入不同的參數,比如:

  • support-cmds all
  • init recovery
  • support-cmds pre-build build post-build
  • pre-build recovery
  • support-cmds build post-build
  • build recovery
  • support-cmds post-build;
  • post-build recovery

其中:recovery可以替換成./build.sh支持的任意選項,比如allbuildrootubootkernel等。

腳本入口代碼如下:

......# 跳轉到<SDK>目錄
cd "$SDK_DIR"# 比如 LOG_FILE_NAME=$(*.sh腳本名稱,比如00-config)-(參數1,比如`support-cmds`)_2024-07-08_21-12-25
LOG_FILE_NAME="$(basename "${0%.sh}")-$1_$(date +"%F_%H-%M-%S")"case "$1" inhelp | h | -h | --help | \?) usage ;;make-targets) make_targets; exit 0 ;;make-usage) make_usage; exit 0 ;;usage) usage_hook; exit 0 ;;support-cmds)   # 將參數列表向左移動一位,去掉了第一個參數(support-cmds),剩下的參數存儲在$@中(即all)shift{ALL_CMDS="$INIT_CMDS $PRE_BUILD_CMDS $BUILD_CMDS \$POST_BUILD_CMDS"for stage in ${@:-all}; docase $stage ininit) echo "$INIT_CMDS" ;;pre-build) echo "$PRE_BUILD_CMDS" ;;build) echo "$BUILD_CMDS" ;;post-build) echo "$POST_BUILD_CMDS" ;;# 走這里,輸出支持的命令 chip defconfig lunch .*_defconfig olddefconfig savedefconfig menuconfig configall) echo "$ALL_CMDS" ;;   esacdone} | xargs -n 1 | grep -v "^default$" | xargs || true   # 過濾掉單獨包含 "default" 的行,然后將剩余的行作為參數依次傳遞給下一個命令exit 0;;clean)try_func clean_hookexit 0;;init)shifttry_hook init_hook "$INIT_CMDS" $@exit 0;;pre-build)shifttry_hook pre_build_hook "$PRE_BUILD_CMDS" $@exit 0;;build)shifttry_hook build_hook "$BUILD_CMDS" $@exit 0;;post-build)shifttry_hook post_build_hook "$POST_BUILD_CMDS" $@exit 0;;
esacif [ "$DRY_RUN" ]; thenecho "Environment 'DRY_RUN' ignored!"unset DRY_RUN
fiif [ "$2" = cmds ]; thenexport DRY_RUN=1
fi
2.1.1 入參為support-cmds all

如果入參為support-cmds all,代碼會進入support-cmds分支;

support-cmds)    # 走這里# 將參數列表向左移動一位,去掉了第一個參數(support-cmds),剩下的參數存儲在$@中(即all)shift{ALL_CMDS="$INIT_CMDS $PRE_BUILD_CMDS $BUILD_CMDS \$POST_BUILD_CMDS"for stage in ${@:-all}; docase $stage ininit) echo "$INIT_CMDS" ;;pre-build) echo "$PRE_BUILD_CMDS" ;;build) echo "$BUILD_CMDS" ;;post-build) echo "$POST_BUILD_CMDS" ;;# 走這里,輸出支持的命令 chip defconfig lunch .*_defconfig olddefconfig savedefconfig menuconfig configall) echo "$ALL_CMDS" ;;   esacdone} | xargs -n 1 | grep -v "^default$" | xargs || true   # 過濾掉單獨包含 "default" 的行,然后將剩余的行作為參數依次傳遞給下一個命令exit 0;;

首先輸出ALL_CMDS,最后執行exit 0退出*.sh腳本(build-heleper是在*.sh腳本中以source方式執行的)文件的執行;

通過代碼分析,我們可以得到入參第一個參數為support-cmds的命令均會進入該分支:

  • support-cmds all

  • support-cmds pre-build build post-build

  • support-cmds build post-build

  • support-cmds post-build;

腳本根據support-cmds之后的參數來執行不同的處理邏輯,比如:

  • all:輸出所有支持的命令,包括INIT_CMDSPRE_BUILD_CMDSBUILD_CMDSPOST_BUILD_CMDS
  • pre-build:輸出PRE_BUILD_CMDS
  • build:輸出BUILD_CMDS
  • post-build:輸出POST_BUILD_CMDS

由于<SDK>/device/rockchip/common/build-hooks目錄下各個*.sh腳本中支持的INIT_CMDSPRE_BUILD_CMDSBUILD_CMDSPOST_BUILD_CMDS是不同的,因此每個*.sh腳本在使用source命令執行build-helper腳本時輸出也是不同的。

我們以如下命令為例:

CMDS="$(run_build_hooks support-cmds all | xargs)"

xargs 將會把 run_build_hooks support-cmds all 的輸出(echo函數的輸出)捕獲,并通過管道傳遞給 xargs。由于run_build_hooks函數會依次執行*.sh腳本,因此CMDS存儲的就是這些腳本執行的輸出結果。

2.1.2 入參為init recovery

如果入參為init recovery,代碼會進入init分支;

init)# 將參數列表向左移動一位,去掉了第一個參數(support-cmds),剩下的參數存儲在$@中(即recovery)shifttry_hook init_hook "$INIT_CMDS" $@exit 0;;

接著會執行try_hook函數,具體參考《try_hook分析》。

2.1.3 入參為pre-build recovery

如果入參為pre-build recovery,代碼會進入pre-build分支;

pre-build)shifttry_hook pre_build_hook "$PRE_BUILD_CMDS" $@exit 0;;

接著會執行try_hook函數,具體參考《try_hook分析》。

2.1.4 入參為build recovery

如果入參為build recovery,代碼會進入build分支;

build)shifttry_hook build_hook "$BUILD_CMDS" $@exit 0;;

接著會執行try_hook函數,具體參考《try_hook分析》。

2.1.5 入參為post-build recovery

如果入參為post-build recovery,代碼會進入post-build分支;

post-build)shifttry_hook post_build_hook "$POST_BUILD_CMDS" $@exit 0;;

接著會執行try_hook函數,具體參考《try_hook分析》。

2.2 err_handler

error處理函數,腳本中的任何命令失敗(即退出狀態為非零),那么就會調用err_handler函數;

err_handler()
{# 如果參數1為空,則獲取最近一次執行命令的退出狀態ret=${1:-$?}# 退出狀態為0,直接返回[ "$ret" -eq 0 ] && return# 入參參數2為空,輸出調用棧中的第二個函數名echo "ERROR: Running $0 - ${2:-${FUNCNAME[1]}} failed!"# 輸出錯誤狀態碼,以及發生錯誤的行號echo "ERROR: exit code $ret from line ${BASH_LINENO[0]}:"# 如果參數3為空,輸出當前正在執行的命令echo "    ${3:-$BASH_COMMAND}"echo "ERROR: call stack:"for i in $(seq 1 $((${#FUNCNAME[@]} - 1))); doSOURCE="${BASH_SOURCE[$i]}"LINE=${BASH_LINENO[$(( $i - 1 ))]}echo "    $(basename "$SOURCE"): ${FUNCNAME[$i]}($LINE)"doneexit $ret
}
trap 'err_handler' ERR
set -eE
2.3 try_hook

try_hook函數接收3個參數:

  • 參數1是被調用的hook函數;
  • 參數2是hook函數支持的命令數組CMDS
  • 參數3是當前需要執行的任一選項命令;

函數定義如下:

try_hook()
{# init_hook、pre_build_hook等FUNC=$1# 將參數列表向左移動一位,去掉了第一個參數(init_hook)shift# 支持的命令數組CMDS="$1"# 將參數列表向左移動一位,去掉了第二個參數(CMDS)shift# 配以default開頭并且后面跟著一個空格或者直接是行尾的行if echo "$CMDS" | grep -qE "^default( |$)"; thenOPTS="default $@"else# 一般走這里OPTS="$@ default"fi# 檢查名為$FUNC的函數是否存在并且可執行,不存在或不可執行則return 0type $FUNC >/dev/null 2>/dev/null || return 0# 遍歷選項數組for opt in $OPTS; do#匹配包含:cmds這個字符串,并且后面緊跟著一個冒號:,或者是行尾的地方  比如opt="recovery:cmds",IS_DRY_RUN=$(echo $opt | grep -E ":cmds(:|$)" || true)# 遍歷命令數組for cmd in $CMDS; do# NOTE: There might be patterns in commands .... default 姑且認為查找opt、cmd相等的項ARGS="$(echo $opt | grep -E "^$cmd(:|$)" | \tr ':' ' ' || true)"[ "$ARGS" ] || continue# D優先使用DRY_RUN變量的值,如果DRY_RUN不存在或為空,則根據IS_DRY_RUN的值來決定是否展開為1。DRY_RUN=${DRY_RUN:-${IS_DRY_RUN:+1}} \try_func $FUNC $ARGSdonedone
}

這里我們姑且認為雙層循環就是查找在CMDS數組中支持的選項,然后調用try_func函數,并傳遞參數$FUNC以及匹配到的選項。

2.3.1 入參為init_hook

build-helper腳本中入參為init_hook的函數調用如下:

try_hook init_hook "$INIT_CMDS" $@

由于<SDK>/device/rockchip/common/build-hooks目錄下各個*.sh腳本中定義的init_hook函數、INIT_CMDS數組不相同,盡管$@參數一樣但是執行的結果均是不同的。

注意:實際上只有部分*.sh腳本定義有init_hook,如果該函數未定義的try_hook會直接return 0

2.3.2 入參為pre_build_hook

build-helper腳本中入參為pre_build_hook的函數調用如下:

try_hook pre_build_hook "$PRE_BUILD_CMDS" $@

由于<SDK>/device/rockchip/common/build-hooks目錄下各個*.sh腳本中定義的pre_build_hook函數、PRE_BUILD_CMDS數組不相同,盡管$@參數一樣但是執行的結果均是不同的。

注意:實際上只有部分*.sh腳本定義有pre_build_hook,如果該函數未定義的try_hook會直接return 0

2.3.3 入參為build_hook

build-helper腳本中入參為build_hook的函數調用如下:

try_hook build_hook "$BUILD_CMDS" $@

由于<SDK>/device/rockchip/common/build-hooks目錄下各個*.sh腳本中定義的build_hook函數、BUILD_CMDS數組不相同,盡管$@參數一樣但是執行的結果均是不同的。

注意:實際上只有部分*.sh腳本定義有build_hook,如果該函數未定義的try_hook會直接return 0

2.3.4 入參為post_build_hook

build-helper腳本中入參為post_build_hook的函數調用如下:

try_hook post_build_hook "$POST_BUILD_CMDS" $@

由于<SDK>/device/rockchip/common/build-hooks目錄下各個*.sh腳本中定義的post_build_hook函數、POST_BUILD_CMDS數組不相同,盡管$@參數一樣但是執行的結果均是不同的。

注意:實際上只有部分*.sh腳本定義有post_build_hook,如果該函數未定義的try_hook會直接return 0

2.3 try_func

嘗試調用某個函數,函數接收多個參數:

  • 參數1為函數將要調用的hook函數,比如usage_hookinit_hookpre_build_hook build_hook post_build_hook clean_hook;這些函數一般在查看使用說明、構建的初始化階段、預構建、構建、構建后、以及清理時被觸發;
  • 參數2為hook函數的入參,傳入hook函數所支持的命令選項;

比如try_func init_hook default,將會執行init_hook default

# try_func init_hook default
try_func()
{# 檢查名為init_hook的函數是否存在并且可執行,不存在或不可執行則return 0type $1 >/dev/null 2>/dev/null || return 0# Don't log these hookscase "${1%_hook}" in   # 第一個參數移除_hook,即initinit | pre_build)$@          # 執行init_hook defaultreturn 0;;esacif [ "$DRY_RUN" ]; thenDRY_FUNC=$1_dry         # 比如build_hook_drytype $DRY_FUNC >/dev/null 2>/dev/null || return 0# 將參數列表向左移動一位,去掉了第1個參數shift# 函數調用$DRY_FUNC $@return 0fi# 設置日志文件LOG_FILE="$(start_log ${LOG_FILE_NAME%%_*} $LOG_FILE_NAME)"、# 記錄運行日志echo -e "# run func: $@\n" >> "$LOG_FILE"# 運行鉤子并記錄輸出$@ 2>&1 | tee -a "$LOG_FILE"# 處理鉤子執行結果FUNC_RET=${PIPESTATUS[0]}if [ $FUNC_RET -ne 0 ]; thenerr_handler $FUNC_RET "${FUNCNAME[0]} $*" "$@"exit $FUNC_RETfi
}

判斷調用的是不是init_hookpre_build_hook函數,如果是執行該函數并將參數傳入,執行完后return 0

如果是build_hook post_build_hook 等函數,

  • 如果$DRY_RUN存在將會執行$1_dry,比如build_hook_dry 參數2 ,執行完后return 0
  • 否則將會執行如下過程:
    • 設置日志文件:LOG_FILE=output/sessions/latest/$(*.sh腳本名稱,比如00-config)-(參數1,比如support-cmds)_創建時間
    • 記錄運行日志:將# run hook: $@打印到$LOG_FILE中;
    • 運行鉤子并記錄輸出:
      • 調用鉤子函數,并將標準錯誤(2)輸出到(>)到標準輸出(&1);
      • 將標準錯誤輸出重定向到標準輸出,這樣可以確保錯誤信息也被tee命令捕獲到;
      • tee -a將管道中的輸出同時輸出到標準輸出和追加到$LOG_FILE文件中;
    • 處理鉤子執行結果:將管道中上一個命令的退出狀態保存在FUNC_RET變量中,PIPESTATUS[0]Bash 內置的數組,包含了最近一個管道中命令的退出狀態;
      • 如果FUNC_RET不等于 0,則表示有錯誤發生;
      • 調用err_handler數處理錯誤,傳遞錯誤碼、當前函數名以及傳遞給當前函數的所有參數;
      • FUNC_RET的值退出腳本,終止整個構建過程。

三、00-config.sh

00-config.sh腳本位于<SDK>/device/rockchip/common/build-hooks/目錄,這里我們挑選一些重點內容介紹。

3.1 腳本入口

首先執行SDK/device/rockchip/common/build-hooks/build-helper腳本,會將這個腳本的內容直接加載到當前shell環境中執行,build-helper.sh 能夠獲取父文件中的變量和參數;

#  source SDK/device/rockchip/common/build-hooks/build-helper
source "${BUILD_HELPER:-$(dirname "$(realpath "$0")")/../build-hooks/build-helper}"init_hook $@

使用source命令執行腳本的一些注意事項:

  • 環境變量和函數的影響:被執行的腳本可以修改當前shell的環境變量和定義的函數,這些修改將持續影響到當前shell的會話,直到會話結束或者重新定義了這些變量和函數。
  • 退出狀態:被執行的腳本的退出狀態(即最后一個命令的退出狀態)會影響到當前shell。可以通過$?變量來獲取最近一次執行命令的退出狀態;
  • 交互性:與直接執行腳本不同,使用source執行腳本時,不會創建新的shell環境,因此不會有新的子shell進程。這使得它適合于需要腳本和當前shell環境之間相互影響的場景,例如定義函數或設置環境變量;
3.2 usage_hook

usage_hook用于輸出當前腳本支持的命令;

usage_hook()
{echo -e "chip[:<chip>[:<config>]]          \tchoose chip"echo -e "defconfig[:<config>]              \tchoose defconfig"echo -e " *_defconfig                      \tswitch to specified defconfig"echo "    available defconfigs:"ls "$CHIP_DIR/" | grep "defconfig$" | sed "s/^/\t/"echo -e " olddefconfig                     \tresolve any unresolved symbols in .config"echo -e " savedefconfig                    \tsave current config to defconfig"echo -e " menuconfig                       \tinteractive curses-based configurator"echo -e "config                            \tmodify SDK defconfig"
}
3.3 init_hook

init_hook函數在構建的初始化階段被調用,用于處理INIT_CMDS命令;

INIT_CMDS="chip defconfig lunch .*_defconfig olddefconfig savedefconfig menuconfig config default"init_hook()
{case "${1:-default}" inchip) shift; choose_chip $@ ;;lunch|defconfig) shift; choose_defconfig $@ ;;*_defconfig) switch_defconfig "$1" ;;olddefconfig | savedefconfig | menuconfig)prepare_config$MAKE $1;;config)prepare_config$MAKE menuconfig$MAKE savedefconfig;;default) prepare_config ;; # End of init*) usage ;;esac
}

在調用init_hook函數時可以傳入以上任一命令作為選項,init_hook將會執行不同的分支:

  • chip:執行 choose_chip $@函數;
  • lunch|defconfig:執行choose_defconfig $@函數;
  • _defconfig:執行switch_defconfig "$1" 函數;
  • olddefconfig | savedefconfig | menuconfig:執行prepare_config函數;
  • config:執行prepare_config函數、$MAKE menuconfig$MAKE savedefconfig
  • default:執行prepare_config函數;
  • 不滿足以上命令,將會執行usage輸出該腳本用法;
3.3.1 入參為default

執行prepare_config函數;

# 如果板載配置不存在在,選擇板載配置
prepare_config()
{	# 如果<SDK>/device/rockchip/.chip路徑不存在,則執行choose_chip選擇芯片# 如果存在,其指向如下:.chip -> .chips/rk3588/[ -e "$CHIP_DIR" ] || choose_chip# 進入<SDK>/device/rockchipcd "$DEVICE_DIR"# 查找/device/rockchip/.chips目錄下文件,即刪除rk3588   rk3588 -> .chips/rk3588/rm -f $(ls "$CHIPS_DIR")   # ln -rsf .chips/rk3588 .ln -rsf "$(readlink "$CHIP_DIR")" .# 跳轉到SDK目錄cd "$SDK_DIR"# 如果<SDK>/output/defconfig文件不存在或不可讀,則執行choose_chip選擇芯片		if [ ! -r "$RK_DEFCONFIG_LINK" ]; thenecho "WARN: $RK_DEFCONFIG_LINK not exists"choose_defconfigreturn 0fi# 由<SDK>/output/defconfig -> ../device/rockchip/.chips/rk3588/rockchip_rk3588_sige7_defconfig,得到rockchip_rk3588_sige7_defconfigDEFCONFIG=$(basename "$(realpath "$RK_DEFCONFIG_LINK")")# 檢查兩個文件是否不是同一個文件if [ ! "$RK_DEFCONFIG_LINK" -ef "$CHIP_DIR/$DEFCONFIG" ]; thenecho "WARN: $RK_DEFCONFIG_LINK is invalid"choose_defconfigreturn 0fi# 檢查文件$RK_CONFIG是否比$RK_DEFCONFIG_LINK更舊if [ "$RK_CONFIG" -ot "$RK_DEFCONFIG_LINK" ]; thenecho "WARN: $RK_CONFIG is out-dated"$MAKE $DEFCONFIG &>/dev/nullreturn 0fiCONFIG_DIR="$(dirname "$RK_CONFIG_IN")"if find "$CONFIG_DIR" -cnewer "$RK_CONFIG" | grep -q ""; thenecho "WARN: $CONFIG_DIR is updated"$MAKE $DEFCONFIG &>/dev/nullreturn 0fiCFG="RK_DEFCONFIG=\"$(realpath "$RK_DEFCONFIG_LINK")\""if ! grep -wq "$CFG" "$RK_CONFIG"; thenecho "WARN: $RK_CONFIG is invalid"$MAKE $DEFCONFIG &>/dev/nullreturn 0fiif [ "$RK_CONFIG" -nt "${RK_CONFIG}.old" ]; then$MAKE olddefconfig &>/dev/nulltouch "${RK_CONFIG}.old"fi
}
3.1.2 入參為_defconfig

執行switch_defconfig函數:

switch_defconfig()
{DEFCONFIG="$1"[ -f "$DEFCONFIG" ] || DEFCONFIG="$CHIP_DIR/$DEFCONFIG"if [ ! -f "$DEFCONFIG" ]; thenecho "No such defconfig: $1"exit 1fiecho "Switching to defconfig: $DEFCONFIG"rm -f "$RK_DEFCONFIG_LINK"ln -rsf "$DEFCONFIG" "$RK_DEFCONFIG_LINK"DEFCONFIG="$(realpath "$DEFCONFIG")"rm -rf "$CHIP_DIR"ln -rsf "$(dirname "$DEFCONFIG")" "$CHIP_DIR"$MAKE $(basename "$DEFCONFIG")
}

參考文章

[1] Rockchip_Developer_Guide_Linux_Software_CN.pdf

[2] repo介紹與使用

[3] Rockchip_Developer_Guide_Linux_Upgrade_CN.pdf

[4] Rockchip RK3588 - NanoPC-T6開發板介紹

[5] Rockchip RK3399 - NanoPC-T4開發板介紹

[6] Buildroot根文件系統構建

[7] Debian根文件系統構建

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/43959.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/43959.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/43959.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LeetCode //C - 214. Shortest Palindrome

214. Shortest Palindrome You are given a string s. You can convert s to a palindrome by adding characters in front of it. Return the shortest palindrome you can find by performing this transformation. Example 1: Input: s “aacecaaa” Output: “aaacec…

如何在JetBrains中寫Codeforce?

目錄 前言 正文 leetcode 個人喜好 參考資料 具體操作步驟 尾聲 &#x1f52d; Hi,I’m Pleasure1234&#x1f331; I’m currently learning Vue.js,SpringBoot,Computer Security and so on.&#x1f46f; I’m studying in University of Nottingham Ningbo China&#x1f4…

Python函數 之 模塊和包

1.模塊 1, 在Python 中, 每個以 .py 結尾的 Python 代碼?件 都可以稱為是?個模塊。 2, 在模塊中 別?書寫好的功能(變量, 函數, 類)&#xff0c;我們可以拿來直接使?。 3, 我們自己寫的代碼文件&#xff0c; 想要作為模塊讓別?使?, 你的代碼?件名(模塊名) 滿足標識符的規…

物流工業三防平板實時跟蹤貨物位置和狀態

在當今全球化和高度數字化的商業環境中&#xff0c;物流行業的高效運作對于企業的成功和經濟的繁榮至關重要。貨物的準確、實時跟蹤不僅能提高物流效率&#xff0c;還能增強客戶滿意度&#xff0c;降低運營成本。物流工業三防平板的出現&#xff0c;為實現貨物位置和狀態的實時…

全網最適合入門的面向對象編程教程:12 類和對象的 Python 實現-Python 使用 logging 模塊輸出程序運行日志

全網最適合入門的面向對象編程教程&#xff1a;12 類和對象的 Python 實現-Python 使用 logging 模塊輸出程序運行日志 摘要&#xff1a; 本文主要介紹了日志的定義和作用&#xff0c;以及 Python 內置日志處理的 logging 模塊&#xff0c;同時簡單說明了日志等級和 logging …

【人工智能】-- 搜索技術(狀態空間法)

個人主頁&#xff1a;歡迎來到 Papicatch的博客 課設專欄 &#xff1a;學生成績管理系統 專業知識專欄&#xff1a; 專業知識 文章目錄 &#x1f349;引言 &#x1f348;介紹 &#x1f349;狀態空間法 &#x1f348;狀態空間的構成 &#x1f34d;狀態 &#x1f34d;算符…

搜維爾科技:觸覺反饋數據手套CyberGlove擊鼓測試

觸覺反饋數據手套CyberGlove擊鼓測試 搜維爾科技&#xff1a;觸覺反饋數據手套CyberGlove擊鼓測試

辦公助手推薦?

辦公助手來啦&#xff01;? 辦公助手來啦&#xff01;?&#x1f31f; 主要亮點&#x1f4dd; 全新PDF編輯器&#x1f3a8; 豐富的幻燈片版式&#x1f30d; 改進的從右至左顯示&#x1f310; 新增本地化選項 &#x1f4ca; 應用場景在線辦公套件&#x1f4f1; 多平臺支持&…

IEC62056標準體系簡介-1.引言

隨著微電子技術和信息技術的發展&#xff0c;電力系統由智能計量儀表、自動化裝置、現代通信設備等組成的各類系統逐步取代過去由感應系計量表計、手動裝置、人工操作等組成的運行模式。為滿足電力市場變革和用戶管理中的抄表&#xff08;含自動&#xff09;、用戶服務、價格表…

torch.autograd.Function自定義前向傳播和反向傳播

torch.autograd.Function 是 PyTorch 提供的一個接口&#xff0c;用于自定義前向傳播和反向傳播的操作。自定義操作需要繼承 torch.autograd.Function 并重載 forward 和 backward 方法。 下面是一個簡單的示例&#xff0c;展示如何自定義一個平方操作的前向傳播和反向傳播。 …

idea創建dynamic web project

由于網課老師用的是eclipse,所以又得自己找教程了…… 解決方案&#xff1a; https://blog.csdn.net/Awt_FuDongLai/article/details/115523552

20240709每日后端--------最優解決Invalid bound statement (not found)

目標 最優解決Invalid bound statement (not found) 步驟 1、打包 2、查看target下是否成雙成對出現 3、核對無誤后&#xff0c;即可解決問題。

軟考高級里《系統架構設計師》容易考嗎?

我還是22年通過的架構考試。系統架構設計師屬于軟考高級科目&#xff0c;難度比初級和中級都要大&#xff0c;往年的通過率也比較低&#xff0c;一般在10-20%左右。從總體來說&#xff0c;這門科目確實是不好過的&#xff0c;大家如果想要備考系統架構設計師的話&#xff0c;還…

Kithara和OpenCV (一)

Kithara使用 OpenCV 目錄 Kithara使用 OpenCV簡介需求和支持的環境構建 OpenCV 庫使用 CMake 進行配置以與 Kithara 一起工作 使用 OpenCV 庫設置項目運行 OpenCV 代碼圖像采集和 OpenCV自動并行化限制和局限性1.系統建議2.實時限制3.不支持的功能和缺失的功能4.顯示 OpenCV 對…

【技術選型】FastDFS、OSS如何選擇

【技術選型】FastDFS、OSS如何選擇 開篇詞&#xff1a;干貨篇&#xff1a;FastDFS&#xff1a;OSS&#xff08;如阿里云OSS&#xff09;&#xff1a; 總結篇&#xff1a;我是杰叔叔&#xff0c;一名滬漂的碼農&#xff0c;下期再會&#xff01; 開篇詞&#xff1a; 文件存儲該選…

簡談設計模式之原型模式

原型模式是一種創建型設計模式, 用于創建對象, 而不必指定它們所屬的具體類. 它通過復制現有對象 (即原型) 來創建新對象. 原型模式適用于當創建新對象的過程代價較高或復雜時, 通過克隆現有對象來提高性能 原型模式結構 原型接口. 聲明一個克隆自身的接口具體原型. 實現克隆…

【鴻蒙學習筆記】屬性學習迭代筆記

這里寫目錄標題 TextImageColumnRow Text Entry Component struct PracExample {build() {Row() {Text(文本描述).fontSize(40)// 字體大小.fontWeight(FontWeight.Bold)// 加粗.fontColor(Color.Blue)// 字體顏色.backgroundColor(Color.Red)// 背景顏色.width(50%)// 組件寬…

展開說說:Android服務之實現AIDL跨應用通信

前面幾篇總結了Service的使用和源碼執行流程&#xff0c;這里再簡單分析一下如果需要Service跨進程通信該怎樣做。AIDL&#xff08;Android Interface Definition Language&#xff09;Android接口定義語言&#xff0c;用于實現 Android 兩個進程之間進行進程間通信&#xff08…

Clickhouse的聯合索引

Clickhouse 有了單獨的鍵索引&#xff0c;為什么還需要有聯合索引呢&#xff1f;了解過mysql的兄弟們應該都知道這個事。 對sql比較熟悉的兄弟們估計看見這個聯合索引心里大概有點數了&#xff0c;不過clickhouse的聯合索引相比mysql的又有些不一樣了&#xff0c;mysql 很遵循最…

深入解析Spring Boot的application.yml配置文件

目錄 引言Spring Boot配置文件簡介 application.yml的優點 基本結構與語法 YAML語法基礎Spring Boot中application.yml的基本結構 常見配置項詳解 服務器配置數據源配置日志配置其他常見配置 環境配置與Profile 多環境配置激活Profile 高級配置與技巧 屬性的占位符替換自定義配…