相关文章推荐
打盹的桔子  ·  html5 PHP ...·  8 月前    · 
爽快的伤痕  ·  DataGrid 控件概述 - ...·  10 月前    · 
捣蛋的松球  ·  MySQL 笔记 —— ...·  10 月前    · 

1. 我已将本文所述的配置文件上传到Apollo项目的GitHub仓库,大家直接下载使用即可。


2. 我之前已写过一篇博客《使用Visual Studio Code编译Apollo项目》(https://blog.csdn.net/davidhopper/article/details/79349927),这篇文章内容不够全面,这里给出更为详细的阐述。

Apollo项目以其优异的系统架构、完整的模块功能、良好的开源生态及规范的代码风格,受到众多开发者的喜爱和好评。然而,该项目使用命令行编译和调试,不能使用IDE开发,既不直观,也不利于提高效率。我有点追求完美,必须想办法让Apollo项目能在IDE中编译、调试。


Visual Studio Code(以下简称VSCode)是微软第一款支持Linux的轻量级代码编辑器,其功能介于编辑器与IDE之间,但更倾向于一个编辑器。优点是运行速度快,占用内存少,使用方法与Visual Stuio类似。缺点在于,与Visual Studio、QT等IDE相比,功能还不够强大。我认为,Windows中最强大的C++ IDE为Visual Studio,Linux中最强大的C++ IDE为QT。

Apollo项目目前只支持Ubuntu系统(Mac OS系统部分支持),Windows系统中的Visual Studio自然排除在外;此外,Apollo项目使用Bazel编译,而QT目前只支持QMake和CMake工程,因此只能忍痛割爱。在无法使用上述两种IDE的前提下,退而求次选用VSCode。

我写了几个配置文件,允许使用VSCode编译、调试Apollo项目,下面对其进行具体阐述,希望能给广大开发者带来一定的帮助。

{  一  }

使用VSCode编译Apollo项目

首先从GitHub网站(https://github.com/ApolloAuto/apollo)下载Apollo源代码,可以使用git命令下载,也可以直接通过网页下载压缩包。源代码下载完成后,将其放置到合适的目录。使用VSCode编译Apollo项目有一个前提,就是在你的机器上已经顺利安装了Docker。Apollo之前版本提供了一个“install_docker.sh”脚本文件,因为很多开发者反映可能出错,Apollo项目组已将该文件移除。现在要安装Docker就只能参考Docker官方网站(https://www.docker.com/)的帮助文档了。

1.1编译方法

打开“Visual Studio Code”,执行菜单命令“文件->打开文件夹”,在弹出的对话框中,选择“Apollo”项目源文件夹,点击“确定”,如下图所示:

之后,执行菜单命令“任务->运行生成任务”或直接按快捷键“Ctrl+Shift+B”(与Visual Studio和QT的快捷键一致)构建工程,若之前没有启动过Docker,则编译时会启动Docker,需在底部终端窗口输入超级用户密码,如下图所示:

1.2配置文件解析

我在.vscode/tasks.json文件中总共配置了四个常见的任务:build the apollo project(构建Apollo项目)、run all unit tests for the apollo project(运行Apollo项目的所有单元测试)、code style check for the apollo project(Apollo项目的代码风格检查)、clean the apollo project(清理Apollo项目)。其中第一个任务是默认生成任务,可以直接按快捷键“Ctr+Shift+B”调用,其他任务可通过执行菜单命令:任务->运行任务(R)…,在弹出的窗口中,选择对应选项即可,如下图所示:

下面是具体的配置内容,请参考里面的注释来调整编译任务以满足你的构建需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
 {
   "version": "2.0.0",
   "tasks": [
       {
           "label": "build the apollo project",
           "type": "shell",
           // 可根据"apollo.sh"提供的选项来调整编译任务,例如:build_gpu
           "command": "bash apollo_docker.sh build",
           "group": {
               "kind": "build",
               "isDefault": true // default building task invoked by "Ctrl+Shift+B"
           },
           // 格式化错误信息
           "problemMatcher": {
               "owner": "cc",
               "fileLocation": [
                   "relative",
                   "${workspaceFolder}"
               ],
               "pattern": {
                   "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
                   "file": 1,
                   "line": 2,
                   "column": 3,
                   "severity": 4,
                   "message": 5
               }
           }
       },
       {
           "label": "run all unit tests for the apollo project",
           "type": "shell",          
           "command": "bash apollo_docker.sh test",
           "problemMatcher": {
               "owner": "cc",
               "fileLocation": [
                   "relative",
                   "${workspaceFolder}"
               ],
               "pattern": {
                   "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
                   "file": 1,
                   "line": 2,
                   "column": 3,
                   "severity": 4,
                   "message": 5
               }
           }
       },
       {
           "label": "code style check for the apollo project",
           "type": "shell",            
           "command": "bash apollo_docker.sh lint",          
           "problemMatcher": {
               "owner": "cc",
               "fileLocation": [
                   "relative",
                   "${workspaceFolder}"
               ],
               "pattern": {
                   "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
                   "file": 1,
                   "line": 2,
                   "column": 3,
                   "severity": 4,
                   "message": 5
               }
           }
       },
       {
           "label": "clean the apollo project",
           "type": "shell",            
           "command": "bash apollo_docker.sh clean",          
           "problemMatcher": {
               "owner": "cc",
               "fileLocation": [
                   "relative",
                   "${workspaceFolder}"
               ],
               "pattern": {
                   "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
                   "file": 1,
                   "line": 2,
                   "column": 3,
                   "severity": 4,
                   "message": 5
               }
           }
       }
   ]
}
1.3.2 编译时长时间停留在“Building: no action running”界面

这是由于当前系统中存在多个不同版本的Docker或者是bazel内部缓存不一致造成的。

解决方法:

按快捷键“Ctrl+C”键终止当前构建过程,在VSCode的命令终端窗口(如果未打开,按快捷键“Ctrl + `”开启),使用下述方法中的任意一种,停止当前运行的Docker:

执行VSCode的菜单命令:任务->运行任务(R)…,在弹出的窗口中,选择 “clean the apollo project”(清理Apollo项目)。待清理完毕后,按快捷键“Ctrl+Shift+B”,重新构建Apollo项目。

1.3.3 编译时出现类似“Another command (pid=2466) is running. Waiting for it to complete…”的错误

这是由于在其他命令行终端进行编译或是在之前编译时按下“Ctrl+C”键强行终止但残留了部分编译进程所引起的。

解决方法:

按快捷键“Ctrl+C”键终止当前构建过程,在VSCode的命令终端窗口(如果未打开,按快捷键“Ctrl + `”开启),使用如下命令终止残留的编译进程:

Apollo项目运行于Docker中,不能在宿主机(所谓宿主机就是运行Docker的主机,因为Docker服务像寄宿于主机中,故有此称呼)中直接使用GDB调试,而必须先在Docker中借助GDBServer创建调试服务进程,再在宿主机中使用GDB连接Docker中的调试服务进程来完成。下面介绍具体操作方法:

2.1前提条件 2.1.1 编译Apollo项目需带调试信息

编译Apollo项目时需使用build或build_gpu等带调试信息的选项,而不能使用build_opt或build_opt_gpu等优化选项。

2.2.2 Docker内部已安装GDBServer

进入Docker后,可使用如下命令查看:

2.2.2 启动待调试模块

启动待调试模块,既可使用命令行操作,也可借助Dreamview界面完成。我肯定喜欢使用Dreamview界面操作了,下面以调试“planning”模块为例进行说明。

打开Chrome浏览器,输入网址:http://localhost:8888/,打开Dreamview界面,打开“SimControl”选项,如下图所示:

2.2.5 使用脚本文件启动GDBServer

我写了两个脚本文件:scripts/start_gdb_server.sh、docker/scripts/dev_start_gdb_server.sh,其中前者用于在Docker内部启动GDBServer,后者直接在宿主机(Docker外部)启动GDBServer。

假设调试planning模块,端口号为1111,scripts/start_gdb_server.sh的使用方法为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/usr/bin/env bash

function print_usage() {
 RED='\033[0;31m'
 BLUE='\033[0;34m'
 BOLD='\033[1m'
 NONE='\033[0m'

 echo -e "\n${RED}Usage${NONE}:
 .${BOLD}/start_gdb_server.sh${NONE} MODULE_NAME PORT_NUMBER"

 echo -e "${RED}MODULE_NAME${NONE}:
 ${BLUE}planning${NONE}: debug the planning module.
 ${BLUE}control${NONE}: debug the control module.
 ${BLUE}routing${NONE}: debug the routing module.
 ..., and so on."

 echo -e "${RED}PORT_NUMBER${NONE}:
 ${NONE}a port number, such as '1111'."
}

if [ $# -lt 2 ];then
   print_usage
   exit 1
fi

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "${DIR}/apollo_base.sh"

MODULE_NAME=$1
PORT_NUM=$2
shift 2

# If there is a gdbserver process running, stop it first.
GDBSERVER_NUMS=$(pgrep -c -x "gdbserver")
if [ ${GDBSERVER_NUMS} -ne 0 ]; then
 sudo pkill -f "gdbserver"
fi

# Because the "grep ${MODULE_NAME}" always generates a process with the name of
# "${MODULE_NAME}", I added another grep to remove grep itself from the output.
# The following command got a wrong result and I can't find the reason.
#PROCESS_ID=$(ps -eo pid,command | grep "${MODULE_NAME}" | grep -v "grep" | awk '{print $1}')
# This one is OK.
PROCESS_ID=$(pgrep -o -x "${MODULE_NAME}")
#echo ${PROCESS_ID}

# If the moudle is not started, start it first.
if [ -z ${PROCESS_ID} ]; then      
 #echo "The '${MODULE_NAME}' module is not started, please start it in the dreamview first. "  
 #exit 1

 # run function from apollo_base.sh
 # run command_name module_name
 run ${MODULE_NAME} "$@"

 PROCESS_ID=$(pgrep -o -x "${MODULE_NAME}")
fi

sudo gdbserver :${PORT_NUM} --attach ${PROCESS_ID}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/usr/bin/env bash

function check_docker_open() {
 docker ps --format "{{.Names}}" | grep apollo_dev 1>/dev/null 2>&1
 if [ $? != 0 ]; then      
   echo "The docker is not started, please start it first. "  
   exit 1  
 fi
}

function print_usage() {
 RED='\033[0;31m'
 BLUE='\033[0;34m'
 BOLD='\033[1m'
 NONE='\033[0m'

 echo -e "\n${RED}Usage${NONE}:
 .${BOLD}/dev_debug_server.sh${NONE} MODULE_NAME PORT_NUMBER"

 echo -e "${RED}MODULE_NAME${NONE}:
 ${BLUE}planning${NONE}: debug the planning module.
 ${BLUE}control${NONE}: debug the control module.
 ${BLUE}routing${NONE}: debug the routing module.
 ..., and so on."

 echo -e "${RED}PORT_NUMBER${NONE}:
 ${NONE}a port number, such as '1111'."
}

if [ $# -lt 2 ];then
   print_usage
   exit 1
fi

check_docker_open

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "${DIR}/../.."
# pwd

xhost +local:root 1>/dev/null 2>&1
#echo $@
docker exec \
   -u $USER \
   -it apollo_dev \
   /bin/bash scripts/start_gdb_server.sh $@
xhost -local:root 1>/dev/null 2>&1
2.4配置文件解析

我对.vscode/launch.json文件作出配置以便能在VSCode中连接Docker中的调试服务进程。此外,为了能在VSCode中直接启动GDBServer,我在.vscode/launch.json文件中添加了一个调试前启动任务:"preLaunchTask": "start gdbserver",该任务对应于.vscode/tasks.json文件中的一个启动GDBServer的任务,因为GDBServer启动后会一直阻塞命令行窗口,且无法通过在命令后面添加&的方式进行后台启动,我只能将其配置为一个VSCode的后台运行任务。

.vscode/launch.json文件的配置内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 {
   "version": "0.2.0",
   "configurations": [
   {
       "name": "C++ Launch",
       "type": "cppdbg",
       "request": "launch",
       // You can change the "planning" to your module name, but it should be
       // same as in gdbserver of the docker container.
       "program": "${workspaceRoot}/bazel-bin/modules/planning/planning",
       // You can change "localhost:1111" to another "IP:port" name, but it
       // should be same as in gdbserver of the docker container.  
       "miDebuggerServerAddress": "localhost:1111",
       // You can change the task to meet your demands in the
       // ".vscode/tasks.json" file (search the label:  
       // "start gdbserver", but the module name and the port
       // number should be consistent with this file.        
       "preLaunchTask": "start gdbserver",
       "args": [],
       "stopAtEntry": false,
       "cwd": "${workspaceRoot}",
       "environment": [],
       "externalConsole": true,
       "linux": {
           "MIMode": "gdb"
       },
       "osx": {
           "MIMode": "gdb"
       },
       "windows": {
           "MIMode": "gdb"
       }
   }
   ]
}
{
          "label": "start gdbserver",
          "type": "shell",
          // you can change the "planning" module name to another one and
          // change the "1111" to another port number.
          // Note: the module name and port number should be same as in
          // the "launch.json" file.
          "command": "bash docker/scripts/dev_start_gdb_server.sh planning 1111",            
          "isBackground": true,
          "problemMatcher": {
              "owner": "custom",
              "pattern": {
                  "regexp": "__________"
              },
              "background": {
                  "activeOnStart": true,
                   // Don't change the following two lines, otherwise the
                  // gdbserver can't run in the background.
                  "beginsPattern": "^Listening on port$",
                  "endsPattern": "^$"
              }
          }                          
      }