----------------------------------------------------------------------------------------------------------------------------
开发板 :ArmSoM-Sige7
开发板
eMMC
:64GB
LPDDR4
:8GB
显示屏 :15.6
英寸HDMI
接口显示屏
u-boot
:2017.09
linux
:5.10
----------------------------------------------------------------------------------------------------------------------------
在《Rockchip RK3588 - Rockchip Linux SDK
编译》我们SDK
的编译流程以及固件升级相关的内容,本节将会对编译脚本进行深入的分析。
一、build.sh
分析
Rockchip Linux SDK
编译命令是由build.sh
脚本实现的,其入口函数为main
函数。
1.1 main
函数
由于main
函数代码内容较长,我们只对重点内容进行分析:
点击查看代码
main()
{
[ -z "$DEBUG" ] || set -x
trap 'err_handler' ERR
set -eE
# Save intial envionments
unset INITIAL_SESSION
INITIAL_ENV=$(mktemp -u)
if [ -z "$RK_SESSION" ]; then
INITIAL_SESSION=1
env > "$INITIAL_ENV"
fi
export LC_ALL=C
export 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 Makefile
case "$@" in
make-targets)
# Chip targets
ls "$CHIPS_DIR"
;&
make-usage)
run_build_hooks "$@"
rm -f "$INITIAL_ENV"
exit 0 ;;
esac
# Log SDK information
MANIFEST="$SDK_DIR/.repo/manifest.xml"
if [ -e "$MANIFEST" ]; then
if [ ! -L "$MANIFEST" ]; then
MANIFEST="$SDK_DIR/.repo/manifests/$(grep -o "[^\"]*\.xml" "$MANIFEST")"
fi
TAG="$(grep -o "linux-.*-gen-rkr[^.\"]*" "$MANIFEST" | \
head -n 1 || true)"
MANIFEST="$(basename "$(realpath "$MANIFEST")")"
echo
echo -e "\e[35m############### Rockchip Linux SDK ###############\e[0m"
echo
echo -e "\e[35mManifest: $MANIFEST\e[0m"
if [ "$TAG" ]; then
echo -e "\e[35mVersion: $TAG\e[0m"
fi
echo
fi
# Prepare firmware dirs
mkdir -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.xml
rm -f envsetup.sh
OPTIONS=${@:-allsave}
# Special handle for chip and defconfig
# e.g. ./build.sh rk3588:rockchip_defconfig
for opt in $OPTIONS; do
if [ -d "$CHIPS_DIR/${opt%%:*}" ]; then
OPTIONS=$(echo "$OPTIONS" | xargs -n 1 | \
sed "s/^$opt$/chip:$opt/" | xargs)
elif echo "$opt" | grep -q "^[0-9a-z_]*_defconfig$"; then
OPTIONS=$(echo "$OPTIONS" | xargs -n 1 | \
sed "s/^$opt$/defconfig:$opt/" | xargs)
fi
done
# Options checking
CMDS="$(run_build_hooks support-cmds all | xargs)"
for opt in $OPTIONS; do
case "$opt" in
help | h | -h | --help | usage | \?) usage ;;
clean:*)
# Check cleanup modules
for m in $(echo ${opt#clean:} | tr ':' ' '); do
grep -wq clean_hook \
"$SCRIPTS_DIR/mk-$m.sh" \
2>/dev/null || usage
done
;&
shell | cleanall)
# Check single options
if [ "$opt" = "$OPTIONS" ]; then
break
fi
echo "ERROR: $opt cannot combine with other options!"
;;
post-rootfs)
if [ "$opt" = "$1" -a -d "$2" ]; then
# Hide other args from build stages
OPTIONS=$opt
break
fi
echo "ERROR: $opt should be the first option followed by rootfs dir!"
;;
*)
# Make sure that all options are handled
if option_check "$CMDS" $opt; then
continue
fi
echo "ERROR: Unhandled option: $opt"
;;
esac
usage
done
# Prepare log dirs
if [ ! -d "$RK_LOG_DIR" ]; then
rm -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
# Drop old logs
cd "$RK_LOG_BASE_DIR"
rm -rf $(ls -t | sed '1,10d')
cd "$SDK_DIR"
# Save initial envionments
if [ "$INITIAL_SESSION" ]; then
rm -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 $OPTIONS
rm -f "$RK_OUTDIR/.tmpconfig*"
# 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
# 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 environments
sed -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 environments
rm -f "$RK_CUSTOM_ENV"
for cfg in $(grep "^RK_" "$RK_INITIAL_ENV" || true); do
env | grep -q "^${cfg//\"/}$" || \
echo "$cfg" >> "$RK_CUSTOM_ENV"
done
# Allow custom environments overriding
if [ -e "$RK_CUSTOM_ENV" ]; then
ln -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"; then
echo -e "\e[31mCustom RK_KERNEL_VERSION ignored!\e[0m"
load_config RK_KERNEL_VERSION
fi
if grep -q "^RK_ROOTFS_SYSTEM=" "$RK_CUSTOM_ENV"; then
echo -e "\e[31mCustom RK_ROOTFS_SYSTEM ignored!\e[0m"
load_config RK_ROOTFS_SYSTEM
fi
fi
fi
source "$PARTITION_HELPER"
rk_partition_init
set +a
export PYTHON3=/usr/bin/python3
export RK_KERNEL_VERSION_REAL=$(kernel_version_real)
# Handle special commands
case "$OPTIONS" in
cleanall)
run_build_hooks clean
rm -rf "$RK_OUTDIR" "$SDK_DIR/rockdev"
finish_build cleanall
exit 0 ;;
clean:*)
MODULES="$(echo ${OPTIONS#clean:} | tr ':' ' ')"
for m in $MODULES; do
"$SCRIPTS_DIR/mk-$m.sh" clean
done
finish_build clean - $MODULES
exit 0 ;;
post-rootfs)
shift
run_post_hooks $@
finish_build post-rootfs
exit 0 ;;
esac
# Save final environments
rm -f "$RK_FINAL_ENV"
env > "$RK_FINAL_ENV"
ln -rsf "$RK_FINAL_ENV" "$RK_OUTDIR/"
# 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.1.1 提示模式和错误处理
调试模式设置:首先通过判断是否设置了环境变量DEBUG
,决定是否启用调试模式。如果 DEBUG
被设置为非空值,则运行 set -x
开启调试模式,这样脚本执行时会显示每一行命令的执行情况。
错误处理:使用 trap
和 ERR
捕获脚本中的错误。一旦发生错误,将调用 err_handler
函数;同时,set -eE
确保如果任何命令失败,脚本会立即退出,并且支持 ERR
跟踪。
保存初始环境变量:接着尝试保存当前的环境变量到一个临时文件中。首先,它解除定义了 INITIAL_SESSION
变量(如果存在),然后使用 mktemp -u
生成一个唯一的临时文件路径。如果 RK_SESSION
变量未设置,则认为这是一个新的会话,并将所有当前环境变量保存到生成的临时文件中。
1.1.2 设置环境变量
接着是设置一系列环境变,这些变量定义了一些重要的目录路径,通常在脚本执行过程中会用到这些路径。
export LC_ALL=C
export 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-targets
和make-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
变量进行特殊处理,用于处理 chip
和defconfig
的情况。它会检查每个参数,并根据特定的条件对其进行转换。
首先:
if [ -d "$CHIPS_DIR/${opt%%:*}" ]; then
OPTIONS=$(echo "$OPTIONS" | xargs -n 1 | \
sed "s/^$opt$/chip:$opt/" | xargs)
检查是否存在以$CHIPS_DIR
开头、后面跟着$opt
变量:
分隔之前的字符串作为目录名, 如果满足;修改 $OPTIONS
变量,替换内容$opt
为 chip:$opt
;
- 比如
./builsh rk3588:rockchip_defconfig
,opt=rk3588:rockchip_defconfig
,处理后OPTIONS=chip:rk3588:rockchip_defconfig
;
接着:
elif echo "$opt" | grep -q "^[0-9a-z_]*_defconfig$"; then
OPTIONS=$(echo "$OPTIONS" | xargs -n 1 | \
sed "s/^$opt$/defconfig:$opt/" | xargs)
fi
如果是以_defconfig
结尾命名,修改 $OPTIONS
变量,替换内容$opt
为 defconfig:$opt
;
- 比如
./builsh rockchip_defconfig
,opt=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; do
case "$opt" in
help | h | -h | --help | usage | \?) usage ;;
clean:*)
# Check cleanup modules
for m in $(echo ${opt#clean:} | tr ':' ' '); do
grep -wq clean_hook \
"$SCRIPTS_DIR/mk-$m.sh" \
2>/dev/null || usage
done
;&
shell | cleanall)
# Check single options
if [ "$opt" = "$OPTIONS" ]; then
break
fi
echo "ERROR: $opt cannot combine with other options!"
;;
post-rootfs)
if [ "$opt" = "$1" -a -d "$2" ]; then
# Hide other args from build stages
OPTIONS=$opt
break
fi
echo "ERROR: $opt should be the first option followed by rootfs dir!"
;;
*)
# Make sure that all options are handled
if option_check "$CMDS" $opt; then
continue
fi
echo "ERROR: Unhandled option: $opt"
;;
esac
usage
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
执行清理工作; - 如果选项是
shell
或cleanall
,执行函数run_build_hooks clean
,该函数则会遍历device/rockchip/common/scripts
下shell
脚本文件,并传入clean
参数函数依次执行; - 如果选项是
post-rootfs
,并且满足上面的条件,则设置OPTIONS=$opt
; - 如果不满足以上条件,则调用
option_check
函数检查给定的命令选项是否在指定的命令列表中,如果匹配失败,则输出错误信息,并调用usage
函数。
1.1.6 准备日志目录
接着是创建编译日志目录:
# Prepare log dirs
if [ ! -d "$RK_LOG_DIR" ]; then
rm -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" ]; then
rm -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 选项检查
# 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.sh
支持的所有命令;
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.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 0
done
done
return 1
}
比如我们给函数传入:
commands="run jump swim"
option_check "$commands" run swim sleep
在这个示例中:
$commands
被传递给CMDS
变量,即CMDS="run jump swim
;run swim sleep
是要检查的选项,即option1=run
、option2=swim
、option3=sleep
。
函数会逐个检查每个选项是否在命令列表CMDS
中,如果找到任意匹配的命令,则返回状态码0;否则返回状态码1。
1.3 run_hooks
run_hooks
在指定目录中查找并执行所有以 .sh
结尾的脚本文件。如果执行任何一个脚本失败,它将调用错误处理函数 err_handler
处理错误,并以失败的返回码退出整个脚本。
run_hooks()
{
# 将第一个参数$1赋值给变量DIR
DIR="$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则执行continue
HOOK_RET=$?
err_handler $HOOK_RET "${FUNCNAME[0]} $*" "$hook $*"
exit $HOOK_RET
done
done
}
比如我们给函数传入:
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*
在该示例中将会按顺序依次执行这些脚本,并传递参数support-cmds all
。
1.3.1 00-config.sh
我们以00-config.sh
脚本执行为例;
# 如果板载配置不存在在,选择板载配置
prepare_config()
{
# 如果<SDK>/device/rockchip/.chip路径不存在,则执行choose_chip选择芯片
# 如果存在,其指向如下:.chip -> .chips/rk3588/
[ -e "$CHIP_DIR" ] || choose_chip
# 进入<SDK>/device/rockchip
cd "$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" ]; then
echo "WARN: $RK_DEFCONFIG_LINK not exists"
choose_defconfig
return 0
fi
# 由<SDK>/output/defconfig -> ../device/rockchip/.chips/rk3588/rockchip_rk3588_sige7_defconfig,得到rockchip_rk3588_sige7_defconfig
DEFCONFIG=$(basename "$(realpath "$RK_DEFCONFIG_LINK")")
# 检查两个文件是否不是同一个文件
if [ ! "$RK_DEFCONFIG_LINK" -ef "$CHIP_DIR/$DEFCONFIG" ]; then
echo "WARN: $RK_DEFCONFIG_LINK is invalid"
choose_defconfig
return 0
fi
# 检查文件$RK_CONFIG是否比$RK_DEFCONFIG_LINK更旧
if [ "$RK_CONFIG" -ot "$RK_DEFCONFIG_LINK" ]; then
echo "WARN: $RK_CONFIG is out-dated"
$MAKE $DEFCONFIG &>/dev/null
return 0
fi
CONFIG_DIR="$(dirname "$RK_CONFIG_IN")"
if find "$CONFIG_DIR" -cnewer "$RK_CONFIG" | grep -q ""; then
echo "WARN: $CONFIG_DIR is updated"
$MAKE $DEFCONFIG &>/dev/null
return 0
fi
CFG="RK_DEFCONFIG=\"$(realpath "$RK_DEFCONFIG_LINK")\""
if ! grep -wq "$CFG" "$RK_CONFIG"; then
echo "WARN: $RK_CONFIG is invalid"
$MAKE $DEFCONFIG &>/dev/null
return 0
fi
if [ "$RK_CONFIG" -nt "${RK_CONFIG}.old" ]; then
$MAKE olddefconfig &>/dev/null
touch "${RK_CONFIG}.old"
fi
}
.......
# 这里实际上就是定义00-config.sh支持的命令
INIT_CMDS="chip defconfig lunch .*_defconfig olddefconfig savedefconfig menuconfig config default"
# init_hook default
init_hook()
{
case "${1:-default}" in
chip) 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
}
# source SDK/device/rockchip/common/build-hooks/build-helper,将会输出00-config.sh支持的命令
source "${BUILD_HELPER:-$(dirname "$(realpath "$0")")/../build-hooks/build-helper}"
init_hook $@
首先执行SDK/device/rockchip/common/build-hooks/build-helper
脚本,会将这个脚本的内容直接加载到当前shell
环境中执行,因此build-helper.sh
能够获取父文件中的变量和参数;
......
# 跳转到<SDK>目录
cd "$SDK_DIR"
# 比如 LOG_FILE_NAME=00-config-support-cmds_2024-07-08_21-12-25
LOG_FILE_NAME="$(basename "${0%.sh}")-$1_$(date +"%F_%H-%M-%S")"
# 参数1为support-cmds
case "$1" in
help | 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}; do
case $stage in
init) 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 config
all) echo "$ALL_CMDS" ;;
esac
done
} | xargs -n 1 | grep -v "^default$" | xargs || true # 过滤掉单独包含 "default" 的行,然后将剩余的行作为参数依次传递给下一个命令
exit 0
;;
clean)
try_func clean_hook
exit 0
;;
init)
shift
try_hook init_hook "$INIT_CMDS" $@
exit 0
;;
pre-build)
shift
try_hook pre_build_hook "$PRE_BUILD_CMDS" $@
exit 0
;;
build)
shift
try_hook build_hook "$BUILD_CMDS" $@
exit 0
;;
post-build)
shift
try_hook post_build_hook "$POST_BUILD_CMDS" $@
exit 0
;;
esac
if [ "$DRY_RUN" ]; then
echo "Environment 'DRY_RUN' ignored!"
unset DRY_RUN
fi
if [ "$2" = cmds ]; then
export DRY_RUN=1
fi
1.3.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}; do
case $stage in
init) 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 config
all) echo "$ALL_CMDS" ;;
esac
done
} | xargs -n 1 | grep -v "^default$" | xargs || true # 过滤掉单独包含 "default" 的行,然后将剩余的行作为参数依次传递给下一个命令
exit 0
;;
首先输出$ALL_CMDS
,最后执行exit 0
退出00-config.sh
脚本文件的执行。
1.3.1.2 入参为init recovery
如果入参为init recovery
,代码会进入init
分支;
init)
# 将参数列表向左移动一位,去掉了第一个参数(support-cmds),剩下的参数存储在$@中(即recovery)
shift
try_hook init_hook "$INIT_CMDS" $@
exit 0
;;
因此将会执行try_hook
函数,函数定义如下:
# try_hook init_hook 'chip defconfig lunch .*_defconfig olddefconfig savedefconfig menuconfig config default' recovery
try_hook()
{
# init_hook
FUNC=$1
# 将参数列表向左移动一位,去掉了第一个参数(init_hook)
shift
# 'chip defconfig lunch .*_defconfig olddefconfig savedefconfig menuconfig config default'
CMDS="$1"
# 将参数列表向左移动一位,去掉了第二个参数(CMDS)
shift
if echo "$CMDS" | grep -qE "^default( |$)"; then
OPTS="default $@"
else
# 走这里OPTS=recovery default
OPTS="$@ default"
fi
# 检查名为$FUNC的函数是否存在并且可执行,不存在或不可执行则return 0
type $FUNC >/dev/null 2>/dev/null || return 0
# 遍历选项:recovery default
for opt in $OPTS; do
IS_DRY_RUN=$(echo $opt | grep -E ":cmds(:|$)" || true)
# 遍历命令:chip defconfig lunch .*_defconfig olddefconfig
for cmd in $CMDS; do
# NOTE: There might be patterns in commands .... default
ARGS="$(echo $opt | grep -E "^$cmd(:|$)" | \
tr ':' ' ' || true)"
[ "$ARGS" ] || continue
DRY_RUN=${DRY_RUN:-${IS_DRY_RUN:+1}} \
try_func $FUNC $ARGS
done
done
}
双层循环匹配下来,只有opt=default
、cmd=default
时会执行try_func $FUNC $ARGS
:
try_func init_hook default
try_func
函数定义如下:
# try_func init_hook default
try_func()
{
# 检查名为init_hook的函数是否存在并且可执行,不存在或不可执行则return 0
type $1 >/dev/null 2>/dev/null || return 0
# Don't log these hooks
case "${1%_hook}" in # 第一个参数移除_hook,即init
init | pre_build)
$@ # 执行init_hook default
return 0
;;
esac
if [ "$DRY_RUN" ]; then
DRY_FUNC=$1_dry
type $DRY_FUNC >/dev/null 2>/dev/null || return 0
shift
$DRY_FUNC $@
return 0
fi
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 ]; then
err_handler $FUNC_RET "${FUNCNAME[0]} $*" "$@"
exit $FUNC_RET
fi
}
经过分析我们知道最终调用的是init_hook default
。
1.3.2 05-partitions.sh
我们以05-partitions.sh
脚本执行为例;
......
# 这里实际上就是定义05-partitions.sh支持的命令
PRE_BUILD_CMDS="print-parts mod-parts edit-parts new-parts insert-part del-part move-part rename-part resize-part"
pre_build_hook()
{
check_config RK_PARAMETER || return 0
CMD=$1
shift
case "$CMD" in
print-parts) rk_partition_print $@ ;;
mod-parts) modify_partitions $@ ;;
edit-parts) rk_partition_edit $@ ;;
new-parts) rk_partition_create $@ ;;
insert-part) rk_partition_insert $@ ;;
del-part) rk_partition_del $@ ;;
move-part) rk_partition_move $@ ;;
rename-part) rk_partition_rename $@ ;;
resize-part) rk_partition_resize $@ ;;
*)
normalized_usage
exit 1
;;
esac
finish_build $CMD $@
}
# source SDK/device/rockchip/common/build-hooks/build-helper,将会输出05-partitions.sh支持的命令
source "${BUILD_HELPER:-$(dirname "$(realpath "$0")")/../build-hooks/build-helper}"
pre_build_hook $@
1.3.2.1 入参support-cmds all
如果入参为support-cmds all
,代码会进入support-cmds
分支,执行流程00-config,sh
所介绍。
1.4 run_build_hooks
run_build_hooks
函数作用是运行一些构建过程中的钩子(hooks
),并且记录执行日志。
run_build_hooks()
{
# Don't log these hooks
case "$1" in
init | pre-build | make-* | usage | support-cmds)
run_hooks "$RK_BUILD_HOOK_DIR" $@ || true
return 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 ]; then
err_handler $HOOK_RET "${FUNCNAME[0]} $*" "$@"
exit $HOOK_RET
fi
}
函数执行流程如下:
-
根据传递给函数的第一个参数
$1
进行匹配,如果$1
匹配到init
、pre-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
的值退出脚本,终止整个构建过程。
- 如果
接下来我们以./build.sh recovery
命令为例,介绍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
。
1.4.1 run_build_hooks support-cmds all
比如我们给函数传入support-cmds all
,即获取./build.sh
支持的所有命令:
run_build_hooks support-cmds all
在这个示例中:
$1
匹配到support-cmds
;- 调用
run_hooks
函数,并传递参数<SDK>/device/rockchip/common/build-hooks support-cmds all
;- 将依次执行
<SDK>/device/rockchip/common/build-hooks
目录下的sh
脚本,入参为support-cmds all
; - 输出每个
sh
脚本支持的命令;
- 将依次执行
- 所有
sh
脚本执行完,直接return 0
;
1.4.2 run_build_hooks init recovery
比如我们给函数传入 init recovery
,则进行recovery
相关的初始化工作:
run_build_hooks init recovery
在这个示例中:
$1
匹配到init
;- 调用
run_hooks
函数,并传递参数<SDK>/device/rockchip/common/build-hooks init recovery
;- 将依次执行
<SDK>/device/rockchip/common/build-hooks
目录下的sh
脚本,入参为init recovery
; - 在每个
sh
脚本执行try_hook init_hook "$INIT_CMDS" recovery
;- 循环遍历数组
recovery default
,赋值给opt
; - 循环遍历数组
$INIT_CMDS
,赋值给cmd
; - 对于匹配项执行
try_func init_hook $ARGS
(姑且将ARGS
等价成opt
),最终调用sh
脚本中的init_hook $ARGS
方法;
- 循环遍历数组
- 将依次执行
- 所有
sh
脚本执行完,直接return 0
。
1.4.3 run_build_hooks support-cmds pre-build build post-build
1.4.4 run_build_hooks pre-build recovery
1.4.5 run_build_hooks support-cmds build post-build
1.4.6 run_build_hooks build recovery
比如我们给函数传入:
run_build_hooks build recovery
在这个示例中:
(1) $1
无法匹配init
、pre-build
等;
(2) 设置日志文件,并记录运行日志,比如output/sessions/latest/40-recovery-build_2024-07-08_23-05-50
:
# 2024-07-08 22:43:41
# run func: build_hook recovery
==========================================
Start building recovery(buildroot)
==========================================
make: Entering directory '/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot'
GEN /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/Makefile
......
Init Ramdisk: ramdisk
FDT: fdt
you take 0:01.02 to pack recovery image
^[[35mRunning 40-recovery.sh - build_recovery succeeded.^[[0m
(3) 调用run_hooks
函数,传递<SDK>/device/rockchip/common/build-hooks build recovery
。
1.4.7 run_build_hooks support-cmds post-build
1.4.8 run_build_hooks post-buildrecovery
参考文章
[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
根文件系统构建