1. Jazzy版本背景与工作空间到底是个啥
1.1 Jazzy是什么版本,为什么我现在推它
先交代一下背景。ROS2 Jazzy Jalisco是ROS2在2024年发布的长期支持版本,官方支持周期一直延续到2029年,对应的操作系统是Ubuntu 24.04(Noble)。这意味着你如果打算做一个跨几年的机器人项目、毕设或者产品原型,选Jazzy会比选短期支持版本稳很多,起码不会做到一半系统停止维护。
和上一代LTS版本Humble相比,Jazzy在工具链上有几个明显变化:默认编译器是GCC 13.2、Python版本是3.12、CMake最低版本要求也提升了,ament_cmake整套构建工具也做了不少更新。最直接的感受就是:你在网上搜到的大量Humble时代教程,很多命令在Jazzy下跑不通,或者编译报错信息完全不一样。这也是我写这个系列的原因,把自己在Jazzy上从零搭建环境、编译、跑通第一个节点过程中踩过的坑整理出来,希望后来的人少折腾几个晚上。
这篇是第一步,只聊两件事:工作空间怎么建、colcon怎么把包编译起来。先把这两个问题搞透,后续的节点通信、话题、服务、自定义消息接口才有基础。适合刚装好ROS2 Jazzy、正准备开始写第一行代码的读者;如果你已经能熟练用Humble,这篇文章也可以帮你快速锁定Jazzy和旧版本之间的差异点。
1.2 工作空间的结构和它的设计逻辑
ROS2的工作空间,说白了就是你把机器人代码堆在一起的一个根目录。标准模板一般长这样:
ros2_ws/ ├── src/ # 放你自己写的功能包源码,也可以放git clone下来的第三方包 ├── build/ # 编译过程中的中间文件,相当于C++项目的build目录 ├── install/ # 编译完的产物,脚本、库、可执行文件都会安装到这里 └── log/ # 每次编译的日志记录很多新手第一次看到这么多目录会懵,其实这布局的逻辑非常直白:src是你唯一需要手动编辑的地方,build和log基本可以无视,install才是真正被ROS2运行时加载的地方。
为什么要分成build和install两层?我用自己的理解解释一下。build里存的是CMake生成的中间文件和一堆临时对象文件,如果哪天编译出问题,你可以直接删掉build目录重新来,不会伤到源码。install是“安装之后的状态”,它把每个包的可执行文件、库文件、launch脚本、配置文件统一放到一个干净的地方,这样运行时候只需要把install目录告诉ROS2,它就能通过一套机制找到所有包。这种设计的好处是:你不需要把一堆动态库拷贝到系统目录里,也不用担心不同工作空间之间的文件互相污染,想切换项目就切换source路径,非常干净。
另外提一个容易踩坑的细节:如果你有一个包暂时不想被编译,可以在它的源码根目录放一个名为COLCON_IGNORE的空文件,colcon会自动跳过这个目录。这个技巧在工程后期特别有用,比如你git clone了一堆参考包,但只想编译其中几个,不想把整个项目拖进来,用这个文件做标记比每次都删目录方便得多。
1.3 从catkin到colcon,编译体系到底变了啥
用惯了ROS1的朋友应该知道,早期ROS1用catkin_make把整个工作空间“一把梭”编译,后来出现了catkin tools,支持增量编译。到了ROS2,官方直接换了新工具叫colcon,它的设计思路更通用,不仅支持ROS2的ament_cmake、ament_python,还支持纯CMake项目、纯Python项目,甚至可以混合编译。
colcon相对于catkin_make的优势,我用一个最直观的对比来说明:
| 对比项 | catkin_make | colcon |
|---|---|---|
| 构建系统 | 仅限ROS1的catkin | 通用的ament_cmake / CMake / Python |
| 增量编译 | 支持,但多包调度一般 | 支持,且按依赖顺序智能调度 |
| 并行编译 | 需要手动指定 | 默认自动用满CPU核心 |
| 日志管理 | 混在一起 | 每个包一个日志文件 |
| 安装部署 | 生成devel目录 | 生成install目录,更接近最终部署形态 |
我在实际使用中最喜欢的是colcon的日志分隔机制。以前catkin_make编译失败时,一大段报错信息夹杂在几百行输出里,你得往上翻很久。而colcon会把每个包的日志单独放到log/<日期>/目录下,终端只显示Summary一行,提示哪个包成功、哪个包失败、失败日志在哪里。排查问题时直接打开对应包的那份日志文件,效率完全不一样。
搞清楚这些背景之后,下面这节就开始真正动工了。
2. 环境准备与第一次创建工作空间
2.1 检查ROS2环境是否装对:装完别急着敲命令
我见过太多人装完ROS2 Jazzy就急着创建目录、编译包,结果第一步就报错,然后开始怀疑人生。其实在创建工作空间之前,花两分钟确认基础环境没问题,能帮你省掉后面大量排查时间。
先打开一个新终端,执行:
source /opt/ros/jazzy/setup.bash printenv | grep ROS正常的话会输出类似这样几行:
ROS_DISTRO=jazzy ROS_VERSION=2 ROS_PYTHON_VERSION=3这里每个变量都有意义。ROS_DISTRO表示当前环境是哪个发行版;ROS_VERSION=2说明是ROS2而不是ROS1;ROS_PYTHON_VERSION=3表示ROS2内部使用的是Python3。如果你执行完printenv | grep ROS后什么都没输出,说明source没生效,或者ROS2根本没正确安装。
接着跑两条命令进一步验证:
ros2 --help ros2 pkg listros2 pkg list会列出当前环境中所有可以加载的包。如果这个命令能正常刷出一大串包名,说明ROS2核心环境基本没问题;如果报错或者卡住,那大概率是安装步骤出了问题,这时候不要强行往下走,先回到安装流程把环境弄好再继续。另外特别提醒一点:Jazzy只官方支持Ubuntu 24.04,你在22.04上硬装的Jazzy多半会缺依赖,后续编译各种奇奇怪怪的报错,与其折腾不如直接换系统。
2.2 创建属于自己的工作空间目录
环境验证没问题,接下来正式创建工作空间。我这里直接用一个最常见、也最规范的命名:
mkdir -p ~/ros2_ws/src cd ~/ros2_ws先解释一下mkdir -p这个参数的用途:加上-p后,如果~/ros2_ws已经存在,不会报错,还会顺便把下一级src目录一起创建。有些教程会让你分两条命令mkdir ~/ros2_ws然后mkdir ~/ros2_ws/src,效果一样,但用-p一步到位更省事。
关于工作空间的名字,个人建议就固定用ros2_ws,别起中文名,别带空格,也别放在桌面之类带空格的路径下。这个问题在编译阶段不一定会暴露,但如果你后面要用到Docker、CI流水线、或者把项目分享给队友,路径里有空格会引发一堆莫名其妙的问题。我还见过有人直接把代码放在/home/用户名/桌面/项目这种路径下,结果编译时CMake各种路径解析错误,最后全部挪了一次目录才消停。
目录建好之后,你可以在~/ros2_ws/src里放一个空的测试包,后面编译时不需要在src目录里执行。我刚入门时就犯过这个错误,在src目录下运行colcon build,结果colcon找不到任何包,提示一个空的工作空间。正确的执行位置永远是工作空间的根目录,也就是~/ros2_ws这一层。
2.3 Underlay与Overlay:source这步背后的玄机
“为什么每次打开新终端都要source一下?”这是新手必问的问题。理解这个机制,对排查“编译成功但运行找不到包”这类问题特别关键。
ROS2的环境管理靠的是环境变量叠加机制,你可以把它想成两张透明胶片叠在一起看地图。最底层的那张胶片是系统级的ROS2环境,路径在/opt/ros/jazzy/setup.bash,这叫underlay。你自己的工作空间编译后生成的~/ros2_ws/install/setup.bash是叠在上面的一层,叫overlay。每次source一个setup.bash,实际上是把对应目录里的各种路径(比如AMENT_PREFIX_PATH、PYTHONPATH、LD_LIBRARY_PATH)追加到当前终端的环境变量里。
这里的关键点是:环境变量是进程级别的,每个终端都是一个独立的进程,你在一个终端里source过,另一个终端不会自动继承。所以每次打开新终端,如果要用到ROS2命令和自己的工作空间包,就必须重新source。这也是几乎所有ROS2教程都要求你把source命令写进~/.bashrc的原因,这样每次开终端都会自动加载。
echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc echo "source ~/ros2_ws/install/setup.bash" >> ~/.bashrc source ~/.bashrc但这里一定要小心:如果你在~/.bashrc里同时source了多个工作空间,后source的会覆盖先source的同名包,这相当于“上面那张胶片盖住了下面那张”,你实际运行的可能不是你最新改的那个版本。所以我个人的习惯是:/opt/ros/jazzy/setup.bash写进bashrc,全局自动加载;自己的工作空间需要用时手动source,或者写一个别名快速source。至于两个LTS版本(比如Humble和Jazzy)同时装在机器上的情况,我强烈建议不要把它们都写进bashrc,否则你连ros2 --help都可能跑错版本。
3. 我的第一个功能包:从创建到编译通过
3.1 功能包的最小结构
工作空间是“容器”,真正干活的是功能包。ROS2功能包分为两大类型:ament_cmake类型(主要放C++代码)和ament_python类型(放Python代码)。我建议第一步先用C++包来跑通流程,因为C++包的编译链路更完整,能让你对构建过程的理解更深入;Python包虽然不用编译,但后续涉及自定义消息接口时,你还得回到CMake这套体系里。
创建一个C++功能包的命令如下:
cd ~/ros2_ws/src ros2 pkg create hello_ws --build-type ament_cmake --dependencies rclcpp解释一下这几个参数。hello_ws是包名,建议全小写、不能有连字符(下划线可以);--build-type ament_cmake指定包的构建类型;--dependencies rclcpp告诉ROS2这个包依赖rclcpp库,后面生成的文件里会自动帮你填好依赖关系。生成完的功能包大概长这样:
hello_ws/ ├── CMakeLists.txt ├── package.xml ├── include/ │ └── hello_ws/ # 头文件目录,暂时空的 └── src/ # 源文件目录,暂时空的你可能注意到这里没有main函数,是的,ros2 pkg create只生成一个包的“骨架”,主要代码还是要自己写。但这个骨架已经能编译通过了,你可以在src里放一个简单的节点文件,让这个包真正有点用。
3.2 package.xml和CMakeLists.txt里有哪些坑
创建完包之后,先不要急着写代码,花几分钟把两个核心文件看懂,因为它们是最容易踩坑的地方。
package.xml是包的“身份证”,里面记录包名、版本、作者、许可证、依赖项等信息。最低要求是以下几项不能缺:
<name>:必须和包名一致,就是hello_ws<version>:版本号,默认0.0.0,也可以不改<description>:描述信息,随便填点什么<maintainer>:维护者名字和邮箱,不能为空<license>:许可证,建议写Apache-2.0或者MIT
我的经验是,很多新手创建完包后直接编译,结果报错说description或者license为空,其实就是这里没填。另外<depend>rclcpp</depend>这行表示编译时需要rclcpp、运行时也需要rclcpp,在ROS2中我们一般直接用<depend>这个标签,它会自动同时处理build和exec依赖,比ROS1时代分别写<build_depend>和<exec_depend>简洁很多。
再看CMakeLists.txt,这是C++包编译的核心。最低限度的关键内容:
cmake_minimum_required(VERSION 3.8) project(hello_ws) find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) add_executable(hello_node src/hello_node.cpp) target_link_libraries(hello_node ${rclcpp_LIBRARIES}) ament_target_dependencies(hello_node rclcpp) install(TARGETS hello_node DESTINATION lib/${PROJECT_NAME}) ament_package()我在这里特别提醒两点。第一,target_link_libraries并不是唯一需要写的,在ROS2的ament体系里,ament_target_dependencies(hello_node rclcpp)更关键,它负责把rclcpp的头文件路径、库路径、依赖项全都传递进来。第二,install(TARGETS ...)这行千万不要漏,它决定了编译好的可执行文件会被安装到哪个目录。少了这行,你会发现colcon build提示成功,但ros2 run hello_ws hello_node就是找不到可执行文件。
为什么这里有这么多规则?因为ROS2的ament_cmake在CMake之上封装了一层“包的安装和发现机制”。它不仅要编译出可执行文件,还要把可执行文件放到ROS2能搜到的标准位置(lib/<包名>/),同时把包的信息注册到AMENT的索引里。所以只编译不安装,等于你做好了菜但没端上桌,客人自然找不到。
3.3 写一个最小节点,验证整条链路
现在在~/ros2_ws/src/hello_ws/src/目录下新建一个hello_node.cpp文件,写入最简单的一段代码:
#include "rclcpp/rclcpp.hpp" int main(int argc, char** argv) { rclcpp::init(argc, argv); auto node = rclcpp::Node::make_shared("hello_node"); RCLCPP_INFO(node->get_logger(), "Hello ROS2 Jazzy!"); rclcpp::shutdown(); return 0; }这段代码干什么?前三行是ROS2 C++程序的标准初始化流程;auto node = rclcpp::Node::make_shared("hello_node")创建了一个名为hello_node的节点;RCLCPP_INFO是打印日志的宏,作用类似printf,但会带上节点名和时间戳;最后rclcpp::shutdown()正常退出。
然后修改CMakeLists.txt,把刚才那几行关键内容加进去。注意add_executable里的src/hello_node.cpp路径要和你实际文件名一致。这里我不推荐直接复制网上完整模板,最好在ros2 pkg create生成的文件基础上改,因为不同版本生成的模板会有细微差异,直接覆盖容易漏掉Jazzy特有的配置。
3.4 真正执行colcon build:从编译到运行
所有文件准备好之后,回到工作空间根目录执行:
cd ~/ros2_ws colcon build --packages-select hello_ws--packages-select参数表示这次只编译hello_ws这一个包,后续如果你工作空间里有很多包,用这个参数可以只编译你修改过的部分,大幅节省时间。第一次编译建议不加任何额外参数,让colcon按默认配置跑一遍,确保基础流程没问题。
顺利的话,终端最后会显示:
Summary: 1 package finished [x.xxs]这表示编译成功。然后执行:
source install/setup.bash ros2 run hello_ws hello_node如果看到终端打印出Hello ROS2 Jazzy!,恭喜,你的第一个ROS2程序已经跑通了。
这里要展开说一下--symlink-install这个参数,我建议从第二次编译开始就习惯性加上:
colcon build --packages-select hello_ws --symlink-install它的作用是:install目录里的文件不是复制品,而是指向源码的软链接。对于launch文件、Python脚本、配置文件这类内容,你修改源码后不需要重新编译,文件会自动同步。但要注意,C++源码改了以后依然要重新编译,因为可执行文件是编译出来的二进制,不是脚本。这个参数在开发阶段能救你很多次,特别是调试launch文件时,改一行存盘就能直接运行,不用再等colcon跑一遍。
4. Jazzy下最常见的四个编译问题排查复盘
4.1 colcon命令找不到,或者版本不对
这是所有问题里最容易解决的,但也最常见。执行colcon build时报colcon: command not found,说明你的系统里没有安装colcon,这跟ROS2主包是分开的。在Ubuntu 24.04下用apt安装即可:
sudo apt install python3-colcon-common-extensions这里我特别建议不要用pip install colcon-common-extensions来装。原因在于:pip安装的包是放在用户Python环境里的,如果之后创建了虚拟环境,或者系统里存在多个Python版本,ROS2可能引用错环境,导致colcon找不到或者加载一堆不相干的依赖。Ubuntu桌面版默认是禁止pip操作系统Python环境的,硬装会提示externally-managed-environment错误,没必要为了装一个工具破坏系统包管理机制。
还有一个常见情况是:colcon --version能输出版本号,但编译时提示某个内部模块缺失。这个多半是你之前用pip装过旧版colcon,和apt版本冲突了。处理办法是先把pip版的卸载干净,再用apt安装,或者直接用apt的版本覆盖。
4.2 编译时找不到ament_cmake或rclcpp
初始化CMake阶段报错,最常见的一类信息是:
CMake Error at /usr/share/cmake-3.28/Modules/FindPackageHandleStandardArgs.cmake:230 (message): Could NOT find ament_cmake (missing: ament_cmake_DIR)这句话翻译成人话就是:CMake在搜索依赖库的路径里没找到ament_cmake。99%的原因是你没有source ROS2的环境。在你执行colcon build的同一个终端里,先运行:
source /opt/ros/jazzy/setup.bash然后检查环境变量是否生效:
printenv | grep AMENT_PREFIX_PATH如果这个变量非空,说明ROS2环境已经加载了,再去执行colcon build就不会报这个错了。这里我想多啰嗦一句:很多人喜欢用sudo colcon build来解决权限问题,这绝对是灾难的开始。sudo会把当前用户的环境变量全部丢掉,ROS2在root环境下根本找不到ament_cmake,而且编译产物会变成root所有,之后你自己都没权限操作install目录里的东西。记住,colcon build永远不要在root权限下执行。
还有一种情况是环境变量正常,但依然找不到某个特定包。这时候要先确认这个包有没有装。Jazzy的完整桌面版装的包比较全,但如果你装的是ros-base最小版,很多工具包都没有,需要自己补装:
sudo apt install ros-jazzy-<包名>4.3 编译成功但ros2 run找不到功能包
这类问题最让人抓狂:编译明明显示1 package finished,但执行ros2 run hello_ws hello_node却提示Package 'hello_ws' not found。
原因几乎可以锁定为你没有source当前工作空间的install目录,或者终端跑到了别的shell、别的目录,导致ROS2不知道去哪找这个包。解决流程如下:
# 确认当前ROS发行版 echo $ROS_DISTRO # source工作空间环境 source ~/ros2_ws/install/setup.bash # 检查当前环境是否包含该工作空间 echo $COLCON_PREFIX_PATH # 搜索系统里是否有这个包 ros2 pkg list | grep hello_wsCOLCON_PREFIX_PATH是colcon往环境里写入的一个重要变量,它记录了你source过的所有工作空间路径。如果这个变量是空的,说明install/setup.bash根本没生效。另外提醒一下,ros2 run从当前终端的环境变量里查找包,和你当前在哪个目录执行无关,只和source状态有关。所以就算你站在~/ros2_ws目录下,不source照样找不到包。
个人建议把下面这两行加到~/.bashrc末尾:
source /opt/ros/jazzy/setup.bash source ~/ros2_ws/install/setup.bash这样每个新终端都会自动加载环境。但这种方式有一个副作用:如果你同时开发多个工作空间,后source的会把先source的同名包覆盖掉。我的处理习惯是,针对单个项目单独写一个env.sh脚本,里面只source当前项目的依赖链,开终端时手动source这个脚本,避免全局环境被污染。
4.4 Python虚拟环境和conda带来的诡异问题
这个坑在Jazzy+Ubuntu 24.04上特别突出,因为Jazzy默认的Python版本是3.12,很多Python依赖都要求比较新的环境。如果你机器上装了Anaconda、Miniconda或者某个Python虚拟环境,很可能出现以下现象:
ros2 pkg list执行后卡住,半天不输出ros2 run一个Python节点,报ModuleNotFoundError: No module named 'rclpy'- 编译Python包时,跑到某个环节莫名其妙失败
原因很简单:ROS2的很多命令本质上是Python程序,它们需要依赖系统的Python3.12和放在/opt/ros/jazzy/lib/python3.12/site-packages下的rclpy等模块。如果你当前shell激活了conda,那么which python3指向的是conda环境里的Python,ROS2命令在Python路径里找不到自己的库,自然就崩了。
排查方法:
which python3 # 如果输出里有conda或venv路径,说明你处在虚拟环境中解决办法是:在编译和运行ROS2程序之前,先退出虚拟环境:
conda deactivate # 或者 deactivate然后重新打开一个干净终端。我的个人习惯是,把ROS2开发的终端和用来跑机器学习、数据分析的conda环境分开,各用各的标签页,互不干扰。在同一个终端里反复切换环境,早晚会搞混。
4.5 编译报错速查表
| 报错关键词 | 大概率原因 | 处理方式 |
|---|---|---|
command not found: colcon | colcon未安装 | sudo apt install python3-colcon-common-extensions |
Could NOT find ament_cmake | 未source ROS2环境 | 在当前终端执行source /opt/ros/jazzy/setup.bash |
Package 'hello_ws' not found | 未source install目录 | source ~/ros2_ws/install/setup.bash |
ModuleNotFoundError: rclpy | Python环境被虚拟环境污染 | conda deactivate后重新开终端 |
Permission denied | 之前用sudo编译过 | sudo chown -R 用户名:用户名 ~/ros2_ws |
CMake Error: package 'xxx' not found | 某个依赖没装 | sudo apt install ros-jazzy-xxx,把xxx替换为实际包名 |
5. 效率提升:colcon编译的几个实用习惯
5.1 常用编译参数,组合起来更舒服
单条colcon build命令在实际开发中很少直接用,因为它会把整个工作空间从头到尾扫一遍,哪怕你只改了一个文件的注释。我日常最常用的组合是:
colcon build --symlink-install --packages-select hello_ws这条命令只编译hello_ws这一个包,同时把Python脚本和launch文件以软链接方式安装。开发调试阶段,我几乎都用它。
如果某个包需要编译Release版本(比如要做性能测试),可以用:
colcon build --packages-select hello_ws --cmake-args -DCMAKE_BUILD_TYPE=Release这里有个细节要注意:如果你先用Debug模式编译过,再用Release模式重新编译同一个包,build目录里会残留旧配置,可能编译报错。稳妥的做法是先删掉这个包对应的build和install残留,再重新编译:
rm -rf build/hello_ws install/hello_ws colcon build --packages-select hello_ws --cmake-args -DCMAKE_BUILD_TYPE=Release另外一个参数--event-handlers console_direct+可以让你在终端实时看到完整编译输出,不用打开日志文件就能定位错误。缺点是输出会非常长,一般只有编译报错想快速定位时才加上。
5.2 别名配置:把常用命令变成快捷键
重复敲这么长的命令很耽误时间,我建议在~/.bashrc里配几个别名:
alias cbs='colcon build --symlink-install --packages-select' alias cb='colcon build --symlink-install' alias sb='source install/setup.bash'配置之后,日常操作就变成了:
cbs hello_ws # 编译hello_ws sb # source当前工作空间 ros2 run hello_ws hello_node别小看这几个别名,省下来的时间是次要的,关键是减少了敲错参数的概率。我有一次因为没有加--symlink-install,改了launch文件后忘了重新编译,结果运行的一直是旧版本,排查了半天才反应过来。
5.3 一个容易忽略的坑:修改launch文件到底要不要重新编译
这个问题在热词里也出现了,“launch文件修改了需要编译吗”。直接给答案:如果你用--symlink-install编译过,不需要重新编译,直接重新运行launch文件即可。原因是install目录里的launch文件是软链接,指向你源码里的原始文件,你改的就是install里“看到”的那个文件。
但如果你用的是默认编译方式(不带--symlink-install),install目录里的launch文件是编译时复制出来的普通文件,跟源码已经没有任何关联。这时候修改源码里的launch文件,install目录里的还是旧版本,必须重新编译才能生效。
所以我强烈建议从第一天起就养成加--symlink-install的习惯,这个参数带来的便利远大于它的存在感。等哪一天你需要给别人分发编译好的包、或者部署到机器人上的时候,再用不带该参数的正式编译即可。
5.4 每次编译完成后,养成检查Summary的习惯
编译结束后,不要急着运行程序,先看一眼终端末尾的Summary:
Summary: 1 package finished [4.23s]如果某个包编译失败,Summary里会显示1 package failed,并给出日志路径。这时候打开日志文件:
cat log/latest_build/hello_ws/stdout_stderr.log这个日志记录了完整的编译过程,错误信息基本都在最后几十行。我见过不少新手看到屏幕上有一堆红色警告就开始慌,其实大部分warning不影响编译通过,真正致命的是带error:前缀的日志行。先把心态稳住,定位到error,再对照“常见问题速查表”里的场景去排查,大多数问题都能在几分钟内解决。
我在实际操作中最深刻的体会是:工作空间和编译这一层,百分之八十的问题本质上都是环境变量的问题,而不是代码的问题。你写的代码再烂,顶多报个语法错误;但环境变量配错了,编译能通过、运行却找不到包,这类问题最消磨人。所以每次遇到诡异现象,第一反应应该是检查当前终端里ROS2的环境状态,第二步再怀疑代码。这个思路能帮你少走很多弯路。
下一步我打算写“第二步”,内容会聚焦在节点之间如何通过话题通信、怎么写发布订阅程序、以及怎么用launch文件一键启动多个节点。这些内容都会继续基于Jazzy版本实测,争取把每一步都踩成路。如果你在搭建工作空间时遇到我文章里没提到的问题,多看看log目录里的编译日志,再回过头检查package.xml和CMakeLists.txt,大部分答案都在那里。