程序编译安装与卸载
本文最后更新于 2025年5月7日 晚上
本文主要分享了程序编译、安装与卸载的知识点。
编写CmakeLists.txt
在CmakeLists.txt里指定第三方库所在的路径
在CmakeLists.txt
里指定第三方库所在的路径,即指定其编译安装后.cmake
文件所在的路径,例如:
1 |
|
1
find_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
。
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 |
|