Armbian/build脚本分析

1. 脚本目录检测和代码健壮性保证

1.1 程序报错退出脚本

#set -o pipefail  # trace ERR through pipes - will be enabled "soon"
#set -o nounset   ## set -u : exit the script if you try to use an uninitialised variable - one day will be enabled
set -e
set -o errtrace # trace ERR through - enabled
set -o errexit  ## set -e : exit the script if any statement returns a non-true return value - enabled
# Important, go read http://mywiki.wooledge.org/BashFAQ/105 NOW!

1.2. ./compile.sh脚本目录检测

SRC="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
cd "${SRC}" || exit

# check for whitespace in ${SRC} and exit for safety reasons
grep -q "[[:space:]]" <<< "${SRC}" && {
	echo "\"${SRC}\" contains whitespace. Not supported. Aborting." >&2
	exit 1
}

2. single.sh 文件加载(位于./lib)

2.1 检查bash版本是否高于5.x

# The Armbian functions require Bash 5.x.
if [[ "${BASH_VERSINFO:-0}" -lt 5 ]]; then
	echo "Armbian build scripts require Bash 5.x. Go get it..." >&2
	if [[ "${OSTYPE}" == "darwin"* ]]; then
		echo "Armbian build scripts require brew to be installed and working on macOS. (old Bash version)" >&2
		echo "Please install brew, *restart your terminal*." >&2
		echo "Then run 'brew install bash coreutils git', *restart your terminal* and then try again." >&2
		exit 51
	fi
	exit 50
fi

2.2 macOS系统检查

  • 是否以root运行脚本
  • Homebrew 包管理器已安装并正常工作
  • 通过 Homebrew 的 coreutils 安装的 realpath 命令是否可用
  • 更新 PATH 环境变量以包含由 Homebrew 安装的 GNU coreutils 二进制文件。
  • 确保 ${SRC} 变量(应指向构建脚本的源目录)位于用户的主目录中。这对于 macOS 上的 Docker 兼容性很重要。
# If under Darwin, we require brew to be installed and working. Check.
if [[ "${OSTYPE}" == "darwin"* ]]; then
	# Don't allow running as root on macOS.
	if [[ "${EUID}" -eq 0 ]]; then
		echo "Armbian build scripts do not support running as root on macOS." >&2
		echo "Please run as a normal user." >&2
		exit 51
	fi

	if ! command -v brew &> /dev/null; then
		echo "Armbian build scripts require brew to be installed and working on macOS. (brew not available)" >&2
		echo "Please install brew, *restart your terminal*." >&2
		echo "Then run 'brew install bash coreutils git', *restart your terminal* and then try again." >&2
		exit 51
	fi

	# Run "brew --prefix" to check if brew is working.
	if ! brew --prefix &> /dev/null; then
		echo "Armbian build scripts require brew to be installed and working on macOS. (brew --prefix failed)" >&2
		echo "Please install brew, *restart your terminal*." >&2
		echo "Then run 'brew install bash coreutils git', *restart your terminal* and then try again." >&2
		exit 51
	fi

	declare brew_prefix
	brew_prefix="$(brew --prefix)"

	# Make sure realpath is available via brew's coreutils, under ${brew_prefix}
	if ! command -v "${brew_prefix}/opt/coreutils/libexec/gnubin/realpath" &> /dev/null; then
		echo "Armbian build scripts require realpath to be installed via brew's coreutils on macOS. (realpath not available)" >&2
		echo "Please install brew, *restart your terminal*." >&2
		echo "Then run 'brew install bash coreutils git', *restart your terminal* and then try again." >&2
		echo "If that fails, try 'brew reinstall bash coreutils git' and try again." >&2
		exit 51
	fi

	# If under Darwin, we need to set the PATH to include the GNU coreutils.
	# export PATH with new coreutils gnubin's in front.
	export PATH="${brew_prefix}/opt/coreutils/libexec/gnubin:$PATH"
	unset brew_prefix

	# Under Darwin/Docker, the "${SRC}" should be under "${HOME}" -- otherwise Docker will not be able to share/mount it.
	# This is a sanity check to make sure that the user is not trying to build outside of "${HOME}".
	if [[ "${SRC}" != "${HOME}"* ]]; then
		echo "Armbian build scripts require the Armbian directory ($SRC) to be under your home directory ($HOME) on macOS." >&2
		echo "Please clone inside your home directory and try again." >&2
		exit 52
	fi
fi

2.3 检查realpath命令是否存在

if [[ -z "$(command -v realpath)" ]]; then
	echo "Armbian build scripts require coreutils. Go install it." >&2
	exit 53
fi

2.4 防止用户直接运行 single.sh,使用 compile.sh 来启动构建过程。

if [[ $(basename "$0") == single.sh ]]; then
	echo "Please use compile.sh to start the build process"
	exit 255
fi

2.5 通过./lib/library-functions.sh文件加载lib里面的函数脚本

build/lib/library-functions.sh at main · armbian/build · GitHub

3. logging_init函数(位于./lib/functions/logging/logging.sh)

这个函数设置了日志显示的默认行为,包括是否在终端上显示日志、是否启用调试模式等。它还根据终端的背景色调整日志颜色,以提高可读性,并根据运行环境(如 CI/CD 系统、Docker 容器、WSL2 或 macOS)调整日志前缀。

# initialize logging variables. (this does not consider parameters at this point, only environment variables)
logging_init

4. traps_init函数(位于./lib/functions/logging/traps.sh)

初始化一系列的信号捕捉(traps),以便在脚本执行过程中捕捉到特定的信号(如错误、退出、中断和终止信号)时能够执行相应的处理函数 main_trap_handler

function traps_init() {
	# shellcheck disable=SC2034 # Array of cleanup handlers.
	declare -g -a trap_manager_cleanup_handlers=()
	# shellcheck disable=SC2034 # Global to avoid doubly reporting ERR/EXIT pairs.
	declare -g -i trap_manager_error_handled=0
	trap 'main_trap_handler "ERR" "$?"' ERR
	trap 'main_trap_handler "EXIT" "$?"' EXIT
	trap 'main_trap_handler "INT" "$?"' INT
	trap 'main_trap_handler "TERM" "$?"' TERM
}

5. 确保目录安全

# make sure git considers our build system dir as a safe dir (only if actually building)
[[ "${CONFIG_DEFS_ONLY}" != "yes" && "${PRE_PREPARED_HOST}" != "yes" ]] && git_ensure_safe_directory "${SRC}"

6. cli_entrypoint “$@”函数(位于./lib/functions/cli/entrypoint.sh)

6.1 调用跟踪功能

	if [[ "${ARMBIAN_ENABLE_CALL_TRACING}" == "yes" ]]; then
		set -T # inherit return/debug traps
		mkdir -p "${SRC}"/output/call-traces
		echo -n "" > "${SRC}"/output/call-traces/calls.txt
		# See https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html
		trap 'echo "${FUNCNAME[*]}|${BASH_LINENO[*]}|${BASH_SOURCE[*]}|${LINENO}" >> ${SRC}/output/call-traces/calls.txt ;' RETURN
	fi

6.2 映射命令到它们的处理函数以及相关的变量设置

armbian_register_commands位于lib/functions/cli/commands.sh,通过关联数组实现字典的功能

	# Decide what we're gonna do. We've a few hardcoded, 1st-argument "commands".
	declare -g -A ARMBIAN_COMMANDS_TO_HANDLERS_DICT ARMBIAN_COMMANDS_TO_VARS_DICT
	armbian_register_commands # this defines the above two dictionaries

ARMBIAN_COMMANDS_TO_HANDLERS_DICT: 这个关联数组映射了一系列命令字符串到它们对应的处理函数名称。例如,多个与 docker 相关的命令都映射到了同一个处理函数 docker

ARMBIAN_COMMANDS_TO_VARS_DICT: 这个关联数组为特定的命令提供了需要设置的变量。

6.3 处理命令行参数

parse_cmdline_params位于lib/functions/cli/utils-cli.sh,用于将参数分为两类。

	# Process the command line, separating params (XX=YY) from non-params arguments.
	# That way they can be set in any order.
	declare -A -g ARMBIAN_PARSED_CMDLINE_PARAMS=() # A dict of PARAM=VALUE
	declare -a -g ARMBIAN_NON_PARAM_ARGS=()        # An array of all non-param arguments
	parse_cmdline_params "${@}"                    # which fills the above vars.

ARMBIAN_PARSED_CMDLINE_PARAMS,用于存储形式为 PARAM=VALUE 的命令行参数;

ARMBIAN_NON_PARAM_ARGS,用于存储非参数类的命令行参数。

6.4 环境变量应用与日志初始化

apply_cmdline_params_to_env位于lib/functions/cli/utils-cli.sh,用于将键值类型的参数应用到脚本环境。

	# Now load the key=value pairs from cmdline into environment, before loading config or executing commands.
	# This will be done _again_ later, to make sure cmdline params override config et al.
	apply_cmdline_params_to_env "early" # which uses ARMBIAN_PARSED_CMDLINE_PARAMS
	# From here on, no more ${1} or stuff. We've parsed it all into ARMBIAN_PARSED_CMDLINE_PARAMS or ARMBIAN_NON_PARAM_ARGS and ARMBIAN_COMMAND.

	# Re-initialize logging, to take into account the new environment after parsing cmdline params.
	logging_init

6.5 处理配置或配置文件

	declare -a -g ARMBIAN_CONFIG_FILES=()                                            # fully validated, complete paths to config files.
	declare -g ARMBIAN_COMMAND_HANDLER="" ARMBIAN_COMMAND="" ARMBIAN_COMMAND_VARS="" # only valid command and handler will ever be set here.
	declare -g ARMBIAN_HAS_UNKNOWN_ARG="no"                                          # if any unknown params, bomb.
	for argument in "${ARMBIAN_NON_PARAM_ARGS[@]}"; do                               # loop over all non-param arguments, find commands and configs.
		parse_each_cmdline_arg_as_command_param_or_config "${argument}"                 # sets all the vars above
	done

ARMBIAN_CONFIG_FILES,全局的索引数组,用于存储完全验证后的配置文件路径;

parse_each_cmdline_arg_as_command_param_or_config位于lib/functions/cli/utils-cli.sh,检测输入是命令还是配置文件,如果参数被识别为配置文件,将其路径添加到 ARMBIAN_CONFIG_FILES数组中,并将文件名添加到 ARMBIAN_CLI_RELAUNCH_CONFIGS 数组中,将参数设置为ARMBIAN_COMMAND

6.6 命令预处理

在执行最终命令之前,检查是否有需要预先执行的命令,并进行相应的处理。armbian_prepare_cli_command_to_run位于./build/lib/cli/utils-cli.sh

	# If we don't have a command at this stage, we should default either to 'build' or 'docker', depending on OS.
	# Give the chosen command a chance to refuse running, or, even, change the final command to run.
	# This allows for example the 'build' command to auto-launch under docker, even without specifying it.
	# Also allows for launchers to keep themselves when re-launched, yet do something diferent. (eg: docker under docker does build).
	# Or: build under Darwin does docker...
	# each _pre_run can change the command and vars to run too, so do it in a loop until it stops changing.
	declare -g ARMBIAN_CHANGE_COMMAND_TO="${ARMBIAN_COMMAND}"
	while [[ "${ARMBIAN_CHANGE_COMMAND_TO}" != "" ]]; do
		display_alert "Still a command to pre-run, this time:" "${ARMBIAN_CHANGE_COMMAND_TO}" "debug"

		declare -g ARMBIAN_COMMAND_REQUIRE_BASIC_DEPS="no" # reset this before every pre_run, so only the last one wins.
		ARMBIAN_COMMAND="${ARMBIAN_CHANGE_COMMAND_TO}"
		armbian_prepare_cli_command_to_run "${ARMBIAN_COMMAND}"

		ARMBIAN_CHANGE_COMMAND_TO=""
		armbian_cli_pre_run_command
	done

6.7 目录初始化

./output目录和./userpatches

	# Init basic dirs.
	declare -g -r DEST="${SRC}/output" USERPATCHES_PATH="${SRC}"/userpatches # DEST is the main output dir, and USERPATCHES_PATH is the userpatches dir. read-only.
	mkdir -p "${DEST}" "${USERPATCHES_PATH}"                                 # Create output and userpatches directory if not already there
	display_alert "Output directory created! DEST:" "${DEST}" "debug"

6.8 生成UUID

	# set unique mounting directory for this execution.
	# basic deps, which include "uuidgen", will be installed _after_ this, so we gotta tolerate it not being there yet.
	declare -g ARMBIAN_BUILD_UUID
	if [[ "${ARMBIAN_BUILD_UUID}" != "" ]]; then
		display_alert "Using passed-in ARMBIAN_BUILD_UUID" "${ARMBIAN_BUILD_UUID}" "debug"
	else
		if command -v uuidgen 1> /dev/null; then
			ARMBIAN_BUILD_UUID="$(uuidgen)"
		else
			display_alert "uuidgen not found" "uuidgen not installed yet" "info"
			ARMBIAN_BUILD_UUID="no-uuidgen-yet-${RANDOM}-$((1 + $RANDOM % 10))$((1 + $RANDOM % 10))$((1 + $RANDOM % 10))$((1 + $RANDOM % 10))"
		fi
		display_alert "Generated ARMBIAN_BUILD_UUID" "${ARMBIAN_BUILD_UUID}" "debug"
	fi
	declare -g -r ARMBIAN_BUILD_UUID="${ARMBIAN_BUILD_UUID}" # Make read-only
	display_alert "Build UUID:" "${ARMBIAN_BUILD_UUID}" "debug"

6.9 日志和清理处理

# Super-global variables, used everywhere. The directories are NOT _created_ here, since this very early stage. They are all readonly, for sanity.
	declare -g -r WORKDIR_BASE_TMP="${SRC}/.tmp" # a.k.a. ".tmp" dir. it is a shared base dir for all builds, but each build gets its own WORKDIR/TMPDIR.

	declare -g -r WORKDIR="${WORKDIR_BASE_TMP}/work-${ARMBIAN_BUILD_UUID}"                         # WORKDIR at this stage. It will become TMPDIR later. It has special significance to `mktemp` and others!
	declare -g -r LOGDIR="${WORKDIR_BASE_TMP}/logs-${ARMBIAN_BUILD_UUID}"                          # Will be initialized very soon, literally, below.
	declare -g -r EXTENSION_MANAGER_TMP_DIR="${WORKDIR_BASE_TMP}/extensions-${ARMBIAN_BUILD_UUID}" # EXTENSION_MANAGER_TMP_DIR used to store extension-composed functions

	# @TODO: These are used only by rootfs/image actual build, move there...
	declare -g -r SDCARD="${WORKDIR_BASE_TMP}/rootfs-${ARMBIAN_BUILD_UUID}" # SDCARD (which is NOT an sdcard, but will be, maybe, one day) is where we work the rootfs before final imaging. "rootfs" stage.
	declare -g -r MOUNT="${WORKDIR_BASE_TMP}/mount-${ARMBIAN_BUILD_UUID}"   # MOUNT ("mounted on the loop") is the mounted root on final image (via loop). "image" stage
	declare -g -r DESTIMG="${WORKDIR_BASE_TMP}/image-${ARMBIAN_BUILD_UUID}" # DESTIMG is where the backing image (raw, huge, sparse file) is kept (not the final destination)

	# Make sure ARMBIAN_LOG_CLI_ID is set, and unique, and readonly.
	# Pre-runs might change it before this, but if not set, default to ARMBIAN_COMMAND.
	declare -r -g ARMBIAN_LOG_CLI_ID="${ARMBIAN_LOG_CLI_ID:-${ARMBIAN_COMMAND}}"

	# If we're on Linux & root, mount tmpfs on LOGDIR. This has it's own cleanup handler.
	# It also _creates_ the LOGDIR, and the cleanup handler will delete.
	prepare_tmpfs_for "LOGDIR" "${LOGDIR}"

	LOG_SECTION="entrypoint" start_logging_section      # This will create LOGDIR if it does not exist. @TODO: also maybe causes a spurious group to be created in the log file
	add_cleanup_handler trap_handler_cleanup_logging    # cleanup handler for logs; it rolls it up from LOGDIR into DEST/logs
	add_cleanup_handler trap_handler_reset_output_owner # make sure output folder is owned by pre-sudo/pre-Docker user if that's the case

6.10 基础依赖检查

	# @TODO: So gigantic contention point here about logging the basic deps installation.
	if [[ "${ARMBIAN_COMMAND_REQUIRE_BASIC_DEPS}" == "yes" ]]; then
		if [[ "${OFFLINE_WORK}" == "yes" ]]; then
			display_alert "* " "You are working offline!"
			display_alert "* " "Sources, time and host will not be checked"
		else
			# check and install the basic utilities;
			LOG_SECTION="prepare_host_basic" do_with_logging prepare_host_basic # This includes the 'docker' case.
		fi
	fi

6.11 配置文件加载

	# Loop over the ARMBIAN_CONFIG_FILES array and source each. The order is important.
	for config_file in "${ARMBIAN_CONFIG_FILES[@]}"; do
		local config_filename="${config_file##*/}" config_dir="${config_file%/*}"
		display_alert "Sourcing config file" "${config_filename}" "debug"

		# use pushd/popd to change directory to the config file's directory, so that relative paths in the config file work.
		pushd "${config_dir}" > /dev/null || exit_with_error "Failed to pushd to ${config_dir}"

		# shellcheck source=/dev/null
		LOG_SECTION="userpatches_config:${config_filename}" do_with_logging source "${config_file}"

		# reset completely after sourcing config file
		set -e
		#set -o pipefail  # trace ERR through pipes - will be enabled "soon"
		#set -o nounset   ## set -u : exit the script if you try to use an uninitialised variable - one day will be enabled
		set -o errtrace # trace ERR through - enabled
		set -o errexit  ## set -e : exit the script if any statement returns a non-true return value - enabled

		popd > /dev/null || exit_with_error "Failed to popd from ${config_dir}"

		# Apply the params received from the command line _again_ after running the config.
		# This ensures that params take precedence over stuff possibly defined in the config.
		apply_cmdline_params_to_env "after config '${config_filename}'" # which uses ARMBIAN_PARSED_CMDLINE_PARAMS
	done

6.12 执行最终命令

	# Early check for deprecations
	error_if_lib_tag_set # make sure users are not thrown off by using old parameter which does nothing anymore; explain

	display_alert "Executing final CLI command" "${ARMBIAN_COMMAND}" "debug"
	armbian_cli_run_command
	display_alert "Done Executing final CLI command" "${ARMBIAN_COMMAND}" "debug"

	# Build done, run the cleanup handlers explicitly.
	# This zeroes out the list of cleanups, so it"s not done again when the main script exits normally and trap = 0 runs.
	run_cleanup_handlers
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇