08_apollo_scripts_scripts子模块软件架构分析
1. 概述
Apollo scripts子模块是Apollo自动驾驶平台的核心脚本集合,提供完整的开发、构建、测试和运行支持。采用模块化架构设计,将脚本按照功能划分为多个类别,包括基础环境配置、构建工具、模块管理、测试验证、数据处理等,实现开发流程自动化与标准化,简化平台使用复杂度,为开发者提供高效便捷的开发体验。
2. 软件架构图
Apollo scripts子模块采用功能导向的架构设计,将脚本按照功能和使用场景划分为多个模块,各模块之间通过清晰的接口和依赖关系进行交互。整个架构遵循高内聚、低耦合的设计原则,确保了脚本系统的稳定性和可维护性。
3. 调用流程图
Apollo scripts子模块的调用流程涵盖了从环境准备到系统运行的完整开发周期,包括环境配置、项目构建、模块启动和数据处理等多个阶段。整个流程设计清晰,步骤明确,便于开发者理解和使用。
3.1. 开发环境配置流程
3.2. 项目构建流程
3.3. 模块启动流程
3.4. 数据处理流程
3.5. 测试验证流程
4. 详细UML类图
Apollo scripts子模块的UML类图展示了脚本系统的核心组件、它们之间的关系以及各自的职责。类图设计遵循面向对象的设计原则,将脚本的功能抽象为类和方法,便于理解和维护。
4.1. 核心脚本类图
4.2. 脚本执行类图
4.3. 数据处理类图
5. 状态机
Apollo scripts子模块的状态机展示了脚本系统在不同阶段的状态转换和处理流程,包括脚本执行状态机、模块管理状态机和数据处理状态机等。这些状态机设计清晰,状态转换明确,便于开发者理解系统的运行机制和行为。
5.1. 脚本执行状态机
5.2. 模块管理状态机
5.3. 数据处理状态机
6. 源码分析
6.1. 基础环境脚本分析
6.1.1. apollo_base.sh 核心逻辑
bash
#!/usr/bin/env bash
# apollo_base.sh - Apollo基础脚本
# 设置Apollo根目录
export APOLLO_ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# 设置CyberRT路径
export CYBER_PATH="${APOLLO_ROOT_DIR}/cyber"
export LD_LIBRARY_PATH="${CYBER_PATH}/lib:${LD_LIBRARY_PATH}"
export PATH="${CYBER_PATH}/bin:${APOLLO_ROOT_DIR}/bin:${PATH}"
# 设置Bazel配置
export BAZEL_CONFIG="--config=cuda"
export BAZEL_OUTPUT_BASE="${APOLLO_ROOT_DIR}/.cache/bazel"
# 设置Python路径
export PYTHONPATH="${APOLLO_ROOT_DIR}:${PYTHONPATH}"
# 定义通用函数
# 检查命令是否存在
function check_command() {
local cmd="$1"
if ! command -v "${cmd}" > /dev/null 2>&1; then
echo "错误: 命令 ${cmd} 不存在"
return 1
fi
return 0
}
# 检查目录是否存在
function check_dir() {
local dir="$1"
if [ ! -d "${dir}" ]; then
echo "错误: 目录 ${dir} 不存在"
return 1
fi
return 0
}
# 检查文件是否存在
function check_file() {
local file="$1"
if [ ! -f "${file}" ]; then
echo "错误: 文件 ${file} 不存在"
return 1
fi
return 0
}
# 日志函数
function log() {
local level="$1"
local message="$2"
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[${timestamp}] [${level}] ${message}"
}
# 错误处理函数
function error_exit() {
local message="$1"
local exit_code="${2:-1}"
log "ERROR" "${message}"
exit "${exit_code}"
}
# 加载其他配置
if [ -f "${APOLLO_ROOT_DIR}/scripts/env.sh" ]; then
source "${APOLLO_ROOT_DIR}/scripts/env.sh"
fi
# 加载用户配置
if [ -f "${HOME}/.apollo/config" ]; then
source "${HOME}/.apollo/config"
fi
6.1.2. apollo.bashrc 核心逻辑
bash
#!/usr/bin/env bash
# apollo.bashrc - Apollo Shell环境配置
# 加载Apollo基础脚本
if [ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/apollo_base.sh" ]; then
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/apollo_base.sh"
fi
# 设置命令别名
alias ab="apollo_build"
alias ac="apollo_clean"
alias af="apollo_format"
alias al="apollo_lint"
alias ad="dreamview.sh"
alias ap="perception.sh"
alias apl="planning.sh"
# 设置命令自动补全
if [ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/apollo_auto_complete.bash" ]; then
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/apollo_auto_complete.bash"
fi
# 显示欢迎信息
echo ""
echo "Apollo开发环境已配置完成"
echo "使用 'ab' 构建项目,'ac' 清理,'ad' 启动Dreamview"
echo ""
6.2. 构建工具脚本分析
6.2.1. apollo_format.sh 核心逻辑
bash
#!/usr/bin/env bash
# apollo_format.sh - 代码格式化脚本
# 加载基础脚本
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/apollo_base.sh"
# 定义格式化命令
function format_cpp() {
echo "格式化C++代码..."
find . -name "*.cc" -o -name "*.cpp" -o -name "*.h" -o -name "*.hpp" | \
grep -v "third_party" | \
grep -v "bazel-*" | \
xargs clang-format -i
}
function format_python() {
echo "格式化Python代码..."
find . -name "*.py" | \
grep -v "third_party" | \
grep -v "bazel-*" | \
xargs yapf -i
}
function format_bazel() {
echo "格式化Bazel构建文件..."
find . -name "BUILD" -o -name "*.BUILD" -o -name "WORKSPACE" | \
grep -v "third_party" | \
grep -v "bazel-*" | \
xargs buildifier
}
# 主函数
function main() {
local format_all=true
local format_cpp_only=false
local format_python_only=false
local format_bazel_only=false
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case "$1" in
--cpp)
format_cpp_only=true
format_all=false
;;
--python)
format_python_only=true
format_all=false
;;
--bazel)
format_bazel_only=true
format_all=false
;;
-h|--help)
echo "用法: apollo_format.sh [选项]"
echo "选项:"
echo " --cpp 只格式化C++代码"
echo " --python 只格式化Python代码"
echo " --bazel 只格式化Bazel构建文件"
echo " -h, --help 显示帮助信息"
return 0
;;
*)
error_exit "未知选项: $1"
;;
esac
shift
done
# 切换到Apollo根目录
cd "${APOLLO_ROOT_DIR}" || error_exit "无法切换到Apollo根目录"
# 执行格式化
if [[ "${format_all}" == true || "${format_cpp_only}" == true ]]; then
format_cpp
fi
if [[ "${format_all}" == true || "${format_python_only}" == true ]]; then
format_python
fi
if [[ "${format_all}" == true || "${format_bazel_only}" == true ]]; then
format_bazel
fi
echo "代码格式化完成"
}
# 调用主函数
main "$@"
6.2.2. apollo_lint.sh 核心逻辑
bash
#!/usr/bin/env bash
# apollo_lint.sh - 代码检查脚本
# 加载基础脚本
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/apollo_base.sh"
# 定义检查命令
function lint_cpp() {
echo "检查C++代码..."
local cpplint_config="${APOLLO_ROOT_DIR}/.cpplint"
if [ ! -f "${cpplint_config}" ]; then
echo "警告: C++代码检查配置文件不存在,使用默认配置"
cpplint_config=""
else
cpplint_config="--filter=-whitespace,-readability/casting,-build/include_subdir,-runtime/references"
fi
find . -name "*.cc" -o -name "*.cpp" -o -name "*.h" -o -name "*.hpp" | \
grep -v "third_party" | \
grep -v "bazel-*" | \
xargs cpplint ${cpplint_config}
}
function lint_python() {
echo "检查Python代码..."
find . -name "*.py" | \
grep -v "third_party" | \
grep -v "bazel-*" | \
xargs pylint --disable=all --enable=unused-import,unused-variable,unused-argument,missing-docstring
}
# 主函数
function main() {
local lint_all=true
local lint_cpp_only=false
local lint_python_only=false
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case "$1" in
--cpp)
lint_cpp_only=true
lint_all=false
;;
--python)
lint_python_only=true
lint_all=false
;;
-h|--help)
echo "用法: apollo_lint.sh [选项]"
echo "选项:"
echo " --cpp 只检查C++代码"
echo " --python 只检查Python代码"
echo " -h, --help 显示帮助信息"
return 0
;;
*)
error_exit "未知选项: $1"
;;
esac
shift
done
# 切换到Apollo根目录
cd "${APOLLO_ROOT_DIR}" || error_exit "无法切换到Apollo根目录"
# 执行代码检查
if [[ "${lint_all}" == true || "${lint_cpp_only}" == true ]]; then
lint_cpp
fi
if [[ "${lint_all}" == true || "${lint_python_only}" == true ]]; then
lint_python
fi
echo "代码检查完成"
}
# 调用主函数
main "$@"
6.3. 模块管理脚本分析
6.3.1. apollo_action.sh 核心逻辑
bash
#!/usr/bin/env bash
# apollo_action.sh - 模块管理核心脚本
# 加载基础脚本
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/apollo_base.sh"
# 定义模块管理命令
function start_module() {
local module="$1"
echo "启动模块: ${module}..."
# 检查模块是否存在
if [ ! -f "${APOLLO_ROOT_DIR}/modules/${module}/BUILD" ]; then
error_exit "模块 ${module} 不存在"
fi
# 启动模块
cyber_launch start "${APOLLO_ROOT_DIR}/modules/${module}/launch/${module}.launch"
if [ $? -eq 0 ]; then
echo "模块 ${module} 启动成功"
else
error_exit "模块 ${module} 启动失败"
fi
}
function stop_module() {
local module="$1"
echo "停止模块: ${module}..."
# 停止模块
cyber_launch stop "${APOLLO_ROOT_DIR}/modules/${module}/launch/${module}.launch"
if [ $? -eq 0 ]; then
echo "模块 ${module} 停止成功"
else
error_exit "模块 ${module} 停止失败"
fi
}
function restart_module() {
local module="$1"
echo "重启模块: ${module}..."
stop_module "${module}"
start_module "${module}"
}
function status_module() {
local module="$1"
echo "检查模块 ${module} 状态..."
# 检查模块状态
cyber_launch status "${APOLLO_ROOT_DIR}/modules/${module}/launch/${module}.launch"
}
function list_modules() {
echo "列出所有可用模块..."
ls -d "${APOLLO_ROOT_DIR}/modules/*" | grep -v "common" | grep -v "common_msgs" | sort
}
# 主函数
function main() {
local action=""
local module=""
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case "$1" in
start)
action="start"
module="$2"
shift 2
;;
stop)
action="stop"
module="$2"
shift 2
;;
restart)
action="restart"
module="$2"
shift 2
;;
status)
action="status"
module="$2"
shift 2
;;
list)
action="list"
shift
;;
-h|--help)
echo "用法: apollo_action.sh <命令> [模块]"
echo "命令:"
echo " start 启动模块"
echo " stop 停止模块"
echo " restart 重启模块"
echo " status 检查模块状态"
echo " list 列出所有模块"
echo "模块: 模块名称 (如: perception, planning, control)"
return 0
;;
*)
error_exit "未知命令: $1"
;;
esac
done
# 执行命令
case "${action}" in
start)
start_module "${module}"
;;
stop)
stop_module "${module}"
;;
restart)
restart_module "${module}"
;;
status)
status_module "${module}"
;;
list)
list_modules
;;
*)
error_exit "请指定命令"
;;
esac
}
# 调用主函数
main "$@"
6.3.2. dreamview.sh 核心逻辑
bash
#!/usr/bin/env bash
# dreamview.sh - Dreamview启动脚本
# 加载基础脚本
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/apollo_base.sh"
# 定义Dreamview启动命令
function start_dreamview() {
echo "启动Dreamview..."
# 检查Dreamview是否已启动
if pgrep -f "dreamview" > /dev/null; then
echo "Dreamview已经在运行"
return 0
fi
# 启动Dreamview
cd "${APOLLO_ROOT_DIR}" && \
./bazel-bin/modules/dreamview/dreamview --web_host=0.0.0.0 --web_port=8888 &
if [ $? -eq 0 ]; then
echo "Dreamview启动成功,访问 http://localhost:8888"
else
error_exit "Dreamview启动失败"
fi
}
function stop_dreamview() {
echo "停止Dreamview..."
# 停止Dreamview
pkill -f "dreamview"
if [ $? -eq 0 ]; then
echo "Dreamview停止成功"
else
echo "Dreamview未运行"
fi
}
function restart_dreamview() {
echo "重启Dreamview..."
stop_dreamview
start_dreamview
}
# 主函数
function main() {
local action="start"
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case "$1" in
start)
action="start"
shift
;;
stop)
action="stop"
shift
;;
restart)
action="restart"
shift
;;
-h|--help)
echo "用法: dreamview.sh [命令]"
echo "命令:"
echo " start 启动Dreamview (默认)"
echo " stop 停止Dreamview"
echo " restart 重启Dreamview"
echo " -h, --help 显示帮助信息"
return 0
;;
*)
error_exit "未知命令: $1"
;;
esac
done
# 执行命令
case "${action}" in
start)
start_dreamview
;;
stop)
stop_dreamview
;;
restart)
restart_dreamview
;;
*)
error_exit "请指定命令"
;;
esac
}
# 调用主函数
main "$@"
6.4. 数据处理脚本分析
6.4.1. data_record.py 核心逻辑
python
#!/usr/bin/env python3
# data_record.py - 数据记录脚本
import os
import sys
import time
import argparse
import subprocess
# 加载基础配置
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
# 定义数据记录类
class DataRecorder:
def __init__(self, output_dir, duration=0, topics=None):
self.output_dir = output_dir
self.duration = duration
self.topics = topics or []
self.process = None
self.record_file = None
def start(self):
"""开始录制"""
print(f"开始录制数据,输出目录: {self.output_dir}")
# 创建输出目录
os.makedirs(self.output_dir, exist_ok=True)
# 生成记录文件名
timestamp = time.strftime("%Y%m%d_%H%M%S")
self.record_file = os.path.join(self.output_dir, f"record_{timestamp}.record")
# 构建录制命令
cmd = ["cyber_recorder", "record", "-o", self.record_file]
if self.topics:
cmd.extend(self.topics)
# 执行录制命令
print(f"执行命令: {' '.join(cmd)}")
self.process = subprocess.Popen(cmd)
print(f"数据录制已开始,记录文件: {self.record_file}")
def stop(self):
"""停止录制"""
if self.process:
print("停止录制数据...")
self.process.terminate()
self.process.wait()
self.process = None
print(f"数据录制已停止,记录文件: {self.record_file}")
else:
print("没有正在进行的录制")
def wait(self):
"""等待录制完成"""
if self.process and self.duration > 0:
print(f"等待 {self.duration} 秒...")
time.sleep(self.duration)
self.stop()
# 主函数
def main():
parser = argparse.ArgumentParser(description="Apollo数据记录脚本")
parser.add_argument("-o", "--output", required=True, help="输出目录")
parser.add_argument("-d", "--duration", type=int, default=0, help="录制时长(秒),0表示无限录制")
parser.add_argument("-t", "--topic", action="append", help="要录制的话题,可以多次指定")
args = parser.parse_args()
recorder = DataRecorder(args.output, args.duration, args.topic)
try:
recorder.start()
recorder.wait()
if args.duration == 0:
print("按Ctrl+C停止录制...")
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n接收到中断信号")
finally:
recorder.stop()
print("程序退出")
if __name__ == "__main__":
main()
7. 设计模式
7.1. 命令模式
Apollo scripts子模块采用命令模式来实现脚本的解析和执行,将命令封装为函数或对象,提高了命令的可扩展性和可维护性。
bash
# 命令模式的实现
# 命令注册
COMMANDS=()
# 注册命令
function register_command() {
local name="$1"
local handler="$2"
local description="$3"
COMMANDS["${name}"]="${handler}"
COMMAND_DESCRIPTIONS["${name}"]="${description}"
}
# 执行命令
function execute_command() {
local command="$1"
local args="${@:2}"
if [ -n "${COMMANDS[${command}]}" ]; then
${COMMANDS[${command}]} ${args}
else
echo "未知命令: ${command}"
show_help
return 1
fi
}
# 示例命令注册
register_command "start" start_command "启动模块"
register_command "stop" stop_command "停止模块"
register_command "restart" restart_command "重启模块"
register_command "status" status_command "检查状态"
7.2. 模板方法模式
模板方法模式用于定义脚本执行的骨架,具体步骤由子类或具体实现来完成,提高了代码的复用性和可维护性。
bash
# 模板方法模式的实现
# 基础脚本模板
function script_template() {
# 步骤1: 加载基础配置
load_config
# 步骤2: 解析命令行参数
parse_args "$@"
# 步骤3: 验证环境
validate_environment
# 步骤4: 执行具体逻辑
execute_logic
# 步骤5: 清理资源
cleanup
}
# 具体脚本实现
function build_script() {
function execute_logic() {
# 构建逻辑
echo "执行构建..."
}
script_template "$@"
}
function test_script() {
function execute_logic() {
# 测试逻辑
echo "执行测试..."
}
script_template "$@"
}
7.3. 策略模式
策略模式用于实现不同的算法或策略,根据条件选择合适的策略执行,提高了代码的灵活性和可扩展性。
bash
# 策略模式的实现
# 定义策略
STRATEGIES=()
# 注册策略
function register_strategy() {
local name="$1"
local implementation="$2"
STRATEGIES["${name}"]="${implementation}"
}
# 执行策略
function execute_strategy() {
local strategy="$1"
local args="${@:2}"
if [ -n "${STRATEGIES[${strategy}]}" ]; then
${STRATEGIES[${strategy}]} ${args}
else
echo "未知策略: ${strategy}"
return 1
fi
}
# 示例策略注册
register_strategy "clang_format" format_with_clang
register_strategy "google_format" format_with_google
register_strategy "custom_format" format_with_custom
7.4. 观察者模式
观察者模式用于实现事件的发布和订阅机制,当事件发生时,通知所有订阅者,提高了代码的解耦性和可扩展性。
bash
# 观察者模式的实现
# 观察者注册
OBSERVERS=()
# 注册观察者
function register_observer() {
local event="$1"
local observer="$2"
if [ -z "${OBSERVERS[${event}]}" ]; then
OBSERVERS[${event}]=()
fi
OBSERVERS[${event}]+=("${observer}")
}
# 通知观察者
function notify_observers() {
local event="$1"
local data="$2"
if [ -n "${OBSERVERS[${event}]}" ]; then
for observer in "${OBSERVERS[${event}][@]}"; do
${observer} "${data}"
done
fi
}
# 示例观察者注册
register_observer "build_start" log_build_start
register_observer "build_complete" log_build_complete
register_observer "build_failed" log_build_failed
7.5. 单例模式
单例模式用于确保某个类或对象在系统中只有一个实例,提高了资源的利用效率和系统的一致性。
bash
# 单例模式的实现
# 单例变量
SINGLETON_INSTANCE=""
# 获取单例实例
function get_singleton() {
if [ -z "${SINGLETON_INSTANCE}" ]; then
# 创建实例
SINGLETON_INSTANCE=$(create_instance)
fi
echo "${SINGLETON_INSTANCE}"
}
# 创建实例
function create_instance() {
# 创建实例的逻辑
echo "singleton_instance_$(date +%s)"
}
# 示例使用
singleton=$(get_singleton)
echo "获取单例: ${singleton}"
# 再次获取
singleton2=$(get_singleton)
echo "再次获取: ${singleton2}"
echo "是否相同: $([ "${singleton}" == "${singleton2}" ] && echo "是" || echo "否")"
8. 总结
Apollo scripts子模块是Apollo自动驾驶平台的核心脚本集合,提供完整的开发、构建、测试和运行支持。采用模块化架构设计,将脚本按照功能划分为多个类别,包括基础环境配置、构建工具、模块管理、测试验证、数据处理等,实现开发流程自动化与标准化,简化平台使用复杂度。通过命令模式、模板方法、策略模式、观察者模式和单例模式等多种设计模式,提高了代码的可维护性和可扩展性。scripts子模块为开发者提供了高效便捷的开发体验,是Apollo平台的重要组成部分。