程序编译安装与卸载
本文最后更新于 2025年6月5日 晚上
本文主要分享了程序编译、安装与卸载的知识点。
编写CmakeLists.txt
${}
在 CMake 中,${Variable} 和 Variable 的区别如下:
${Variable}
${...}是变量引用的语法,表示取变量的值。- 例如:
if(${Variable})实际等价于if(ON)(如果set(Variable ON))。 - 用于需要变量值的地方,比如
message("${Variable}")会输出ON或OFF。
Variable
- 不加
${},在if()语句中,CMake 会把它当作变量名,自动查找其值。 - 例如:
if(Variable)实际等价于if(DEFINED Variable AND Variable),即变量存在且为真时成立。 - 推荐在条件判断中直接写
if(Variable),这样更安全、语义更清晰。
- 区别总结
${Variable}:取变量的值,常用于字符串拼接、赋值、输出等。Variable:变量名,常用于if()判断,CMake 会自动解析其值。
在CmakeLists.txt里指定第三方库所在的路径
在CmakeLists.txt里指定第三方库所在的路径,即指定其编译安装后.cmake文件所在的路径,例如:
1 | |
1find_library(flowfilter_gpu_LIBS NAMES flowfilter_gpu PATHS /usr/local/include)
首先,
set(OpenCV_DIR /usr/local/opencv/opencv345/share/OpenCV)这行代码设置了一个变量OpenCV_DIR,它指向OpenCV库的安装位置。在这个例子中,OpenCV库被安装在/usr/local/opencv/opencv345/share/OpenCV这个路径下。OpenCV_DIR这个变量通常用于指定OpenCV的cmake配置文件的位置,这个文件包含了OpenCV库的版本信息、编译选项等信息。然后,
find_package(OpenCV REQUIRED)这行代码告诉CMake去查找OpenCV库。REQUIRED关键字表示如果CMake不能找到OpenCV库,那么CMake应该停止配置过程并显示错误信息。如果CMake成功找到了OpenCV库,那么它将设置一些变量,例如OpenCV_INCLUDE_DIRS和OpenCV_LIBS,这些变量分别包含了OpenCV的头文件路径和库文件路径,可以在后续的target_include_directories和target_link_libraries命令中使用。
如果没有设置
OpenCV_DIR,find_package命令会在默认的路径下查找OpenCV库。这些默认的路径包括:
- CMake的模块路径(
CMAKE_MODULE_PATH)- CMake的安装前缀(
CMAKE_PREFIX_PATH)- 系统的环境变量路径
具体来说,
find_package会查找名为OpenCVConfig.cmake或opencv-config.cmake的文件,这个文件通常位于OpenCV库的安装目录中。如果你的OpenCV库安装在非标准的位置,或者你有多个版本的OpenCV库并且想要选择一个特定的版本,那么你可以通过设置
OpenCV_DIR来指定OpenCV库的路径。如果没有设置OpenCV_DIR,CMake可能会找到错误的版本或者找不到OpenCV库。
OpenCV_DIR和OpenCV_INCLUDE_DIR是两个不同的变量,它们在CMake中的作用也不同。
OpenCV_DIR是用于指定OpenCV的cmake配置文件位置的变量。find_package命令会使用OpenCV_DIR变量的值作为查找OpenCV配置文件的起始路径。如果OpenCV_DIR被设置,find_package就会直接在这个路径下查找配置文件,而不会在其他路径下查找。
OpenCV_INCLUDE_DIR通常是find_package命令找到OpenCV库后设置的一个变量,它包含了OpenCV的头文件路径。这个变量通常用于target_include_directories命令,以便你的项目可以找到OpenCV的头文件。如果你设置了
OpenCV_INCLUDE_DIR,而不是OpenCV_DIR,然后调用find_package(OpenCV REQUIRED),那么find_package命令可能无法找到正确的OpenCV库,因为它不知道在哪里查找OpenCV的配置文件。在这种情况下,find_package命令可能会找到错误的OpenCV版本,或者找不到OpenCV库。find_package命令找到的结果会覆盖你设置的OpenCV_INCLUDE_DIR变量。总的来说,如果你想要指定OpenCV库的位置,你应该设置
OpenCV_DIR,而不是OpenCV_INCLUDE_DIR。
(在ROS中)编译第三方开源软件需要下载的问题
注意
CmakeList.txt里有没有指定具体版本。在package.xml里也可以看到指定的版本。
其实就类似于在系统中
cmake、make和make install的步骤,只不过这里的第三方库是安装在了ROS工作区里被相互调用,catkin clean后也就删除掉了,而没有安装在系统环境里。也方便使用指定版本的第三方库。
1 | |
或者,将src(这个文件是原本解压下载的第三方源码source的地方,具体名称要看CMakeLists.txt中SOURCE_DIR的设置)中的各个第三方源码都解压好,放到src对应的文件夹中。例如catkin_ws/build/xxx/xxx_src-prefix/src/xxx.tar.gz。
tips
- CMake 区分大小写。
DBoW3/和DBow3/会被识别为不同的文件夹。
cmake
一般流程
1 | |
我个人推荐把第三方库安装在
/usr/local文件夹下进行管理。例如,在/usr/local文件夹下新建文件夹eigen3,后在eigen3文件夹下新建文件夹eigen330、eigen340。
定义编译参数
可在cmake命令后加参数:
1 | |
也可以在CMakeLists.txt文件中定义,例如,启用参数EFFT_USE_FFTW3:
1 | |
或,在CMakeLists.txt文件中:
1 | |
保存cmake输出
ROS:
cmake build的输出在catkin_ws/logs/your_package_name/build.cmake.log、build.make.log里。
cmake命令的输出信息通常在终端中显示,而不是保存在文件中。这些信息包括配置过程中的警告、错误以及其他重要信息。
然而,你可以将cmake命令的输出重定向到一个文件中。例如,你可以使用以下命令将输出保存到一个名为output.txt的文件中:
1 | |
在这个命令中,>操作符将cmake命令的输出重定向到output.txt文件中。如果output.txt文件已经存在,这个命令将覆盖它的内容。如果你想要追加输出到文件中,而不是覆盖它,你可以使用>>操作符,如下所示:
1 | |
请注意,这些命令只会捕获标准输出,而不会捕获错误输出。如果你也想要捕获错误输出,你可以使用2>&1,如下所示:
1 | |
在这个命令中,2>&1将错误输出重定向到标准输出,然后>操作符将标准输出重定向到output.txt文件中。这样,output.txt文件将包含所有的输出,包括错误信息。
make
1 | |
cmake --install .与make install的区别:
cmake --install .和make install都是用来安装编译好的程序的命令,但它们在使用的构建系统和工作方式上有所不同。
make install是GNU Make的命令,它依赖于Makefile中的install目标。这个install目标通常会将编译好的二进制文件、库文件、头文件等复制到系统的指定位置,如/usr/local/bin、/usr/local/lib等。这个命令通常在使用GNU Autotools或者手写Makefile的项目中使用。
cmake --install .是CMake的命令,它会执行CMakeLists.txt文件中定义的安装规则。这个命令在CMake 3.15及以后的版本中可用,它是cmake -P cmake_install.cmake的一个更简洁的替代。这个命令的好处是它不依赖于特定的构建系统,可以在任何CMake支持的构建系统中使用。总的来说,这两个命令的功能是类似的,但
cmake --install .更加通用,不依赖于特定的构建系统。
卸载
如果因为反复./configure xx然后make会导致安装路径混乱,sudo make install失败。所以如果路径设错了又已经make完,需要make clean来清除一下。如果想把自己刚刚make install安装的卸载掉,可以在那个目录里直接用sudo make uninstall。
1 | |
make uninstall是一个常见的 makefile 目标,它的主要作用是删除由make install命令安装的文件。当你运行
make install命令时,通常会将一些文件(例如可执行文件、库文件、头文件等)复制到系统的某些目录下(例如/usr/local/bin、/usr/local/lib等)。make uninstall命令就是用来删除这些文件的。然而,需要注意的是,并不是所有的 makefile 都提供
make uninstall目标。如果 makefile 没有提供这个目标,运行make uninstall命令将会导致错误。在运行
make uninstall命令之前,你应该查看 makefile 或者相关的文档,以确认这个命令是否可用,以及它会删除哪些文件。
cat install_manifest.txt | sudo xargs rm和sudo make uninstall都是用来删除由make install命令安装的文件的。然而,它们的工作方式有所不同。
cat install_manifest.txt | sudo xargs rm:这个命令会读取install_manifest.txt文件,这个文件通常由make install命令生成,包含了所有被安装的文件的列表。然后,它会使用xargs rm命令删除这些文件。这个命令不依赖于 makefile,只要install_manifest.txt文件存在,就可以使用。sudo make uninstall:这个命令会执行 makefile 中的uninstall目标。这个目标通常会删除所有被make install命令安装的文件。然而,需要注意的是,并不是所有的 makefile 都提供uninstall目标。如果 makefile 没有提供这个目标,运行sudo make uninstall命令将会导致错误。总的来说,这两个命令的功能是相似的,但是
cat install_manifest.txt | sudo xargs rm命令更为直接,不依赖于 makefile。而sudo make uninstall命令则需要 makefile 提供uninstall目标。
make clean和make distclean是两个常见的 makefile 目标,它们的功能取决于 makefile 的编写者如何定义它们。但是,通常它们的功能如下:
make clean:这个命令通常用于删除所有由 makefile 生成的文件。这通常包括编译产生的对象文件(.o 或 .obj 文件)和编译器生成的中间文件。但是,它通常不会删除配置文件或者 makefile 文件。make distclean:这个命令通常用于将目录恢复到初始状态。除了删除make clean会删除的文件,它还会删除配置文件和 makefile 文件。这个命令通常在你想要重新配置和编译一个项目时使用。需要注意的是,这两个命令的具体行为取决于 makefile 的编写者。在使用这些命令之前,你应该查看 makefile 或者相关的文档,以了解这些命令的具体行为。
tips
*.cmake文件
*.cmake文件是 CMake 的脚本文件,用于定义构建逻辑、模块化配置、共享配置等。CMakeLists.txt是 CMake 的顶级配置文件,而*.cmake通常作为辅助文件被CMakeLists.txt引用。- 使用
include、find_package或add_subdirectory可以在项目中引用*.cmake文件。 - 它们可以帮助组织复杂的构建逻辑,使项目更易于维护和扩展。
增量编译
cmake
cmake 命令是增量的,它会根据当前的构建状态和修改情况进行适当的更新。
- 如果上一次执行失败了:直接重新运行
cmake即可。 - 如果修改了
CMakeLists.txt:直接运行cmake即可,它会自动检测和更新。 - 只有在构建目录出问题、切换编译器/生成器、或需要彻底清理时,才需要删除
build文件夹并重新生成。
make
增量编译是指在编译过程中,仅重新编译那些自上次编译以来发生了更改的源文件,而不重新编译所有文件的过程。这种方式可以显著减少编译时间,尤其是在大型项目中。
make的工作原理
- 依赖关系:
make依赖于一个名为Makefile的文件(或用户指定的文件)来定义目标文件(例如可执行程序)和它们的依赖关系(例如源文件)。- 通过分析依赖关系,
make知道哪些文件需要重新构建。
- 时间戳:
make通过比较目标文件(比如.o文件或可执行文件)和依赖文件(比如.cpp或.h文件)的时间戳来判断是否需要重新编译。- 如果源文件或头文件的修改时间比目标文件的新,
make会重新编译。
需要重新cmake吗?
- 只修改代码:直接运行
make。- 修改构建配置或
CMakeLists.txt:需要重新运行cmake。
如果只修改了 C++ 源代码文件,而没有修改 CMakeLists.txt 文件或其他影响构建配置的文件,则可以直接执行以下命令进行增量编译:
1 | |
原因:
CMake会在生成的构建系统(如 Makefile)中记录依赖关系。- 如果只修改了源代码文件,
make会根据依赖关系自动重新编译受影响的文件,而无需重新运行cmake。
如果你修改了以下内容,则需要重新运行 cmake:
CMakeLists.txt文件:- 添加/删除源文件。
- 修改编译选项(如
-std=c++17)。 - 添加/删除库依赖。
- 构建目录被清理:
- 如果删除了
build目录或其中的文件,则需要重新运行cmake。
- 如果删除了
- 更改构建配置:
- 切换编译模式(如从
Debug切换到Release)。
- 切换编译模式(如从
在这些情况下,完整的命令是:
1 | |
tips
- 当你修改了
CMakeLists.txt后直接运行make,它会自动触发cmake来重新生成 Makefile。这种行为是由 CMake 和 Make 的依赖追踪机制决定的,目的是确保构建系统始终保持最新状态。具体的调用方式是由 CMake 内部生成的构建规则决定的,通常是等效于cmake命令与最初配置时的参数。 - 当你执行完
cmake .. -D<变量>=<值>后,这些变量值会被缓存。如果你修改了CMakeLists.txt并重新运行cmake ..,这些变量值不会变化,除非:- 你在命令行中显式重新指定了新的值。
- 你手动清空了缓存(删除
CMakeCache.txt),或使用了cmake .. -U<variable>来清除指定变量。 - 你运行了
cmake .. -D<new_value>覆盖之前的值。
- 等等。
set变量的作用域
普通变量的作用域
在 CMake 中,普通变量的作用域是定义它的 CMakeLists.txt 文件所在的目录。
- 这些变量仅在当前目录的 CMakeLists.txt 和由
include引入的文件中可见。 - 如果使用了
add_subdirectory()引入子目录,普通变量不会自动传递到子目录。
1 | |
子目录中的变量传递
如果希望变量在父目录中可见,可以用 PARENT_SCOPE 指定变量的作用域。
1 | |
在父目录中,MY_VARIABLE 将可见。
函数作用域
- 在函数中定义的变量,其作用域仅限于函数内部,函数外不可见。这是为了避免污染全局命名空间。
- 如果需要将函数内部的变量传递到函数外部,可以使用
PARENT_SCOPE。
1 | |
使用CACHE设置全局变量
如果需要在整个 CMake 项目中都能访问某个变量,可以通过 CACHE 将变量设置为全局变量。
CMake的 CACHE变量可以通过 set命令定义,支持以下类型:
- BOOL:布尔值,用于启用或禁用功能。
- STRING:字符串,用于存储一般文本。
- PATH:指向目录的路径。
- FILEPATH:指向文件的路径。
- INTERNAL:内部变量,不对用户显示。
1 | |
变量的优先级
如果在不同作用域中定义了同名变量,CMake 会优先使用当前作用域的变量,而不是父目录的变量。
1 | |