编译脚本

This commit is contained in:
daiqingshuang 2025-03-19 10:59:56 +08:00
parent 6935d0fac5
commit 1441c2a559

587
build_openwrt.sh Normal file
View File

@ -0,0 +1,587 @@
#!/usr/bin/env bash
#====================================================
# OpenWrt 构建管理脚本
# 功能:提供交互式菜单,可选择执行特定操作
# 更新2024-02-26
#====================================================
#---------------基础配置---------------#
# 日志目录设置
LOGS_ROOT="openwrt_logs"
SESSION_DIR="${LOGS_ROOT}/$(date +%Y%m%d_%H%M%S)"
LOG_FILE="${SESSION_DIR}/build.log"
#---------------清理配置---------------#
# 需要移除的目录列表
declare -a REMOVE_DIRS=(
)
# 需要移除的包列表
declare -a REMOVE_PACKAGES=(
"feeds/luci/applications/luci-app-mosdns"
"feeds/packages/net/alist"
"feeds/packages/net/adguardhome"
"feeds/packages/net/smartdns"
)
#---------------自定义包配置---------------#
# 定义自定义包配置,格式:[包名]=[目标目录]=[仓库URL]=[分支]=[是否使用--depth 1]
declare -A CUSTOM_PACKAGES=(
["partexp"]="package/luci-app-partexp=https://github.com/sirpdboy/luci-app-partexp=main=false"
["openclash"]="package/openclash=https://github.com/vernesong/OpenClash=dev=true"
["zerotier"]="package/luci-app-zerotier=https://nanako.site/gitea/Nanako/luci-app-zerotier.git=main=false"
["5gsupport"]="package/openwrt-app-actions=https://github.com/Siriling/openwrt-app-actions.git=main=true"
["design"]="package/luci-theme-design=https://github.com/SAENE/luci-theme-design.git=js=false"
["mt76"]="package/firmware/mt76=https://github.com/openwrt/mt76.git=master=false"
)
#---------------日志系统---------------#
# 保存原始的标准输出和错误输出
exec {STDOUT_ORIG}>&1
exec {STDERR_ORIG}>&2
# 初始化日志系统
init_logger() {
# 创建日志目录结构
mkdir -p "$SESSION_DIR" || {
echo "无法创建日志目录: $SESSION_DIR"
exit 1
}
# 设置全局变量标记是否记录日志
LOGGING_ENABLED=true
echo "日志保存在: $LOG_FILE"
if [ "$LOGGING_ENABLED" = true ]; then
# 标准输出重定向
exec > >(while read -r line; do
printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" | tee -a "$LOG_FILE"
done)
# 标准错误重定向
exec 2> >(while read -r line; do
printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" | tee -a "$LOG_FILE"
done >&2)
fi
}
# 创建新的编译日志文件
create_build_log() {
local build_type=$1
local timestamp=$(date +%Y%m%d_%H%M%S)
BUILD_LOG_FILE="${SESSION_DIR}/${build_type}_${timestamp}.log"
echo "创建新的编译日志: $BUILD_LOG_FILE"
return 0
}
# 重定向输出到指定的编译日志文件
redirect_to_build_log() {
local log_file=$1
# 保存当前的输出重定向状态
LOGGING_TO_BUILD_LOG=true
# 重定向输出到编译日志文件
exec > >(while read -r line; do
printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" >> "$log_file"
done)
# 重定向错误输出到编译日志文件
exec 2> >(while read -r line; do
printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" >> "$log_file"
done >&2)
}
# 恢复输出到主日志
restore_main_logging() {
LOGGING_TO_BUILD_LOG=false
# 恢复到主日志
if [ "$LOGGING_ENABLED" = true ]; then
# 标准输出重定向
exec > >(while read -r line; do
printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" | tee -a "$LOG_FILE"
done)
# 标准错误重定向
exec 2> >(while read -r line; do
printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" | tee -a "$LOG_FILE"
done >&2)
fi
}
# 禁用日志输出
disable_logging() {
LOGGING_ENABLED=false
exec >&$STDOUT_ORIG
exec 2>&$STDERR_ORIG
}
# 启用日志输出
enable_logging() {
LOGGING_ENABLED=true
# 标准输出重定向
exec > >(while read -r line; do
printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" | tee -a "$LOG_FILE"
done)
# 标准错误重定向
exec 2> >(while read -r line; do
printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" | tee -a "$LOG_FILE"
done >&2)
}
# 设置终端颜色
setup_colors() {
if [[ -t 2 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
else
RED='' GREEN='' YELLOW='' BLUE='' NC=''
fi
}
# 记录日志函数 - 使用此函数来明确日志级别
log() {
local level=$1
local message=$2
local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
local color="" level_str=""
case $level in
"INFO") color="$GREEN"; level_str="INFO" ;;
"WARN") color="$YELLOW"; level_str="WARN" ;;
"ERROR") color="$RED"; level_str="ERROR" ;;
"DEBUG") color="$BLUE"; level_str="DEBUG" ;;
esac
printf "[%s] ${color}[%s]${NC}: %s\n" "$timestamp" "$level_str" "$message"
}
# 同时记录到主日志和编译日志的函数
log_to_both() {
local level=$1
local message=$2
local build_log=$3
# 记录到主日志
log "$level" "$message"
# 记录到编译日志
local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
echo "[$timestamp] [$level]: $message" >> "$build_log"
}
#---------------自定义包管理函数---------------#
# 更新单个自定义包
update_single_package() {
local pkg_name=$1
local config=$2
# 解析配置
local target_dir=$(echo "$config" | cut -d= -f1)
local repo_url=$(echo "$config" | cut -d= -f2)
local branch=$(echo "$config" | cut -d= -f3)
local use_depth=$(echo "$config" | cut -d= -f4)
log "INFO" "更新 $pkg_name..."
# 检查目标目录
if [ -d "$target_dir" ]; then
log "INFO" "移除现有 $target_dir 目录"
rm -rf "$target_dir" || {
log "ERROR" "无法删除 $target_dir 目录"
return 1
}
fi
# 准备克隆命令
local clone_cmd="git clone --progress"
# 添加分支参数 (如果指定)
if [ -n "$branch" ] && [ "$branch" != "master" ]; then
clone_cmd="$clone_cmd -b $branch"
fi
# 添加 depth 参数 (如果需要)
if [ "$use_depth" = "true" ]; then
clone_cmd="$clone_cmd --depth 1"
fi
# 完成克隆命令
clone_cmd="$clone_cmd $repo_url $target_dir"
# 执行克隆
log "INFO" "执行: $clone_cmd"
eval "$clone_cmd" || {
log "ERROR" "克隆 $pkg_name 失败"
return 1
}
log "INFO" "$pkg_name 更新完成"
return 0
}
# 更新自定义包的主函数
update_custom_package() {
local failed_packages=()
local success_count=0
local total_packages=${#CUSTOM_PACKAGES[@]}
log "INFO" "开始更新 $total_packages 个自定义包..."
for pkg_name in "${!CUSTOM_PACKAGES[@]}"; do
update_single_package "$pkg_name" "${CUSTOM_PACKAGES[$pkg_name]}" || {
failed_packages+=("$pkg_name")
continue
}
((success_count++))
done
# 显示更新结果
if [ ${#failed_packages[@]} -eq 0 ]; then
log "INFO" "所有自定义包更新成功 ($success_count/$total_packages)"
return 0
else
log "WARN" "部分包更新失败 (成功: $success_count/$total_packages)"
log "ERROR" "更新失败的包: ${failed_packages[*]}"
return 1
fi
}
# 更新特定自定义包
update_specific_package() {
local pkg_name=$1
if [ -z "$pkg_name" ]; then
log "ERROR" "未指定包名"
return 1
fi
if [ -z "${CUSTOM_PACKAGES[$pkg_name]}" ]; then
log "ERROR" "未找到包配置:$pkg_name"
log "INFO" "可用的包: ${!CUSTOM_PACKAGES[*]}"
return 1
fi
update_single_package "$pkg_name" "${CUSTOM_PACKAGES[$pkg_name]}"
}
#---------------基础功能函数---------------#
# 清理工作区
clean_workspace() {
log "INFO" "开始清理工作区..."
log "INFO" "执行./scripts/feeds clean..."
./scripts/feeds clean || log "WARN" "feeds clean 失败"
log "INFO" "执行rm -rf ./tmp ./staging_dir ./build_dir ./bin ./dl..."
rm -rf ./tmp ./staging_dir ./build_dir ./bin ./dl || log "WARN" "删除临时目录失败"
log "INFO" "工作区清理完成"
}
# 清理编译临时文件
clean_build_temp() {
log "INFO" "开始清理编译临时文件..."
log "INFO" "删除临时编译目录和缓存..."
rm -rf ./tmp ./staging_dir ./build_dir || log "WARN" "删除临时编译目录失败"
log "INFO" "编译临时文件清理完成"
}
# 更新源码
update_source() {
log "INFO" "开始更新源码..."
git pull || { log "ERROR" "源码更新失败"; return 1; }
log "INFO" "源码更新完成"
}
# 更新feeds
update_feeds() {
log "INFO" "开始更新feeds..."
./scripts/feeds update -a && ./scripts/feeds install -a || {
log "ERROR" "Feeds更新失败"
return 1
}
log "INFO" "Feeds更新完成"
return 0
}
# 执行编译命令并仅记录到指定日志文件
run_build_command() {
local cmd=$1
local log_file=$2
local msg=$3
log_to_both "INFO" "$msg" "$log_file"
# 重定向输出到编译日志
redirect_to_build_log "$log_file"
# 执行编译命令
local result=0
eval "$cmd" || result=1
# 恢复输出到主日志
restore_main_logging
return $result
}
# 编译固件
build_firmware() {
# 创建新的编译日志
create_build_log "firmware"
local current_log="$BUILD_LOG_FILE"
log "INFO" "开始编译固件..."
log "INFO" "编译详细日志位置: $current_log"
# 下载依赖
log "INFO" "下载依赖包..."
run_build_command "make download -j$(nproc) V=s" "$current_log" "正在下载依赖包..." || {
log "ERROR" "依赖下载失败,详见日志: $current_log"
return 1
}
# 使用多线程编译
local cpu_cores=$(nproc)
log "INFO" "使用 $cpu_cores 线程开始编译..."
if run_build_command "make -j$cpu_cores V=s" "$current_log" "多线程编译进行中..."; then
log "INFO" "多线程编译成功完成"
return 0
else
log "WARN" "多线程编译失败,清理临时文件后切换为单线程编译..."
# 清理编译临时文件
clean_build_temp
# 创建一个新日志用于单线程编译
create_build_log "firmware_singlethread"
current_log="$BUILD_LOG_FILE"
log "INFO" "单线程编译详细日志位置: $current_log"
# 重新下载依赖
log "INFO" "重新下载依赖包..."
run_build_command "make download -j1 V=s" "$current_log" "单线程模式重新下载依赖..." || {
log "ERROR" "单线程依赖下载也失败,详见日志: $current_log"
return 1
}
if run_build_command "make -j1 V=s" "$current_log" "单线程编译进行中..."; then
log "INFO" "单线程编译成功完成"
return 0
else
log "ERROR" "编译失败,即使在单线程模式下。详见日志: $current_log"
return 1
fi
fi
}
# 清理旧包
clean_packages() {
log "INFO" "开始清理旧包..."
local removed_count=0
local failed_count=0
# 清理目录
for dir in "${REMOVE_DIRS[@]}"; do
if [ -d "$dir" ]; then
rm -rf "$dir" && {
log "INFO" "已删除: $dir"
((removed_count++))
} || {
log "ERROR" "删除失败: $dir"
((failed_count++))
}
fi
done
# 清理包
for pkg in "${REMOVE_PACKAGES[@]}"; do
if [ -d "$pkg" ]; then
rm -rf "$pkg" && {
log "INFO" "已移除: $pkg"
((removed_count++))
} || {
log "ERROR" "删除失败: $pkg"
((failed_count++))
}
fi
done
if [ $failed_count -eq 0 ]; then
log "INFO" "包清理完成,共移除 $removed_count 个包/目录"
return 0
else
log "WARN" "包清理部分完成,成功: $removed_count, 失败: $failed_count"
return 1
fi
}
# 更新所有组件
update_all_components() {
log "INFO" "开始更新所有组件..."
local failed=false
# 更新源码
log "INFO" "执行源码更新..."
update_source || failed=true
# 清理旧包
log "INFO" "执行清理旧包..."
clean_packages || log "WARN" "清理旧包部分失败,继续执行..."
# 更新自定义包
log "INFO" "执行自定义包更新..."
update_custom_package || failed=true
# 更新feeds
log "INFO" "执行feeds更新..."
update_feeds || failed=true
if [ "$failed" = true ]; then
log "WARN" "部分组件更新失败,请查看日志了解详情"
return 1
else
log "INFO" "所有组件更新完成"
return 0
fi
}
# 菜单配置
menuconfig() {
log "INFO" "开始菜单配置..."
disable_logging
make menuconfig
enable_logging
log "INFO" "菜单配置完成"
}
#---------------组合功能函数---------------#
# 完整构建流程
full_build() {
log "INFO" "启动完整构建流程..."
clean_workspace || log "WARN" "工作区清理部分失败,继续执行..."
update_source || { log "ERROR" "源码更新失败,终止构建"; return 1; }
clean_packages || log "WARN" "清理旧包部分失败,继续执行..."
update_custom_package || { log "ERROR" "自定义包更新失败,终止构建"; return 1; }
update_feeds || { log "ERROR" "feeds更新失败终止构建"; return 1; }
log "INFO" "进入菜单配置..."
menuconfig
log "INFO" "开始编译固件..."
build_firmware || {
log "ERROR" "固件编译失败"
return 1
}
log "INFO" "完整构建流程完成"
return 0
}
#---------------菜单函数---------------#
print_menu() {
echo -e "\n${BLUE}OpenWrt 构建管理菜单${NC}"
echo -e "${YELLOW}================================${NC}"
echo -e "${GREEN}1.${NC} 完整构建流程"
echo -e "${GREEN}2.${NC} 清理工作区"
echo -e "${GREEN}3.${NC} 更新所有组件"
echo -e "${GREEN}4.${NC} 清理旧包"
echo -e "${GREEN}5.${NC} 编译固件"
echo -e "${GREEN}6.${NC} 调整配置(menuconfig)"
echo -e "${GREEN}7.${NC} 更新自定义包"
echo -e "${GREEN}8.${NC} 更新特定自定义包"
echo -e "${GREEN}9.${NC} 更新源码"
echo -e "${GREEN}10.${NC} 更新feeds"
echo -e "${GREEN}0.${NC} 退出"
echo -e "${YELLOW}================================${NC}"
echo -ne "请选择操作 [0-10]: "
}
# 显示可用的自定义包
print_package_menu() {
echo -e "\n${BLUE}可用的自定义包${NC}"
echo -e "${YELLOW}================================${NC}"
local i=1
for pkg_name in "${!CUSTOM_PACKAGES[@]}"; do
echo -e "${GREEN}$i.${NC} $pkg_name"
((i++))
done
echo -e "${YELLOW}================================${NC}"
echo -ne "请选择要更新的包 [1-$((i-1))]: "
}
# 选择特定自定义包更新
select_specific_package() {
local pkg_names=("${!CUSTOM_PACKAGES[@]}")
print_package_menu
read -r choice
# 验证选择
if [[ ! "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt ${#pkg_names[@]} ]; then
log "ERROR" "无效选择: $choice"
return 1
fi
local selected_pkg=${pkg_names[$((choice-1))]}
log "INFO" "选择更新包: $selected_pkg"
update_specific_package "$selected_pkg"
}
#---------------主函数---------------#
main() {
# 初始化日志目录和日志系统
init_logger
setup_colors
log "INFO" "OpenWrt 构建管理脚本启动 - 会话日志目录: $SESSION_DIR"
while true; do
print_menu
read -r choice
case $choice in
1) full_build ;;
2) clean_workspace ;;
3) update_all_components ;;
4) clean_packages ;;
5) build_firmware ;;
6) menuconfig ;;
7) update_custom_package ;;
8) select_specific_package ;;
9) update_source ;;
10) update_feeds ;;
0)
log "INFO" "退出脚本"
exit 0
;;
*)
log "WARN" "无效选择: $choice,请重试"
;;
esac
# 操作完成后暂停
if [[ $choice != 0 ]]; then
echo -e "\n${YELLOW}操作完成,按回车键继续...${NC}"
read -r
fi
done
}
# 执行主函数
main "$@"