CMakeLists编译原理
CMake主要是编写CMakeLists.txt文件,然后用cmake命令将CMakeLists.txt文件转化为make所需要的makefile文件,最后用make命令编译源码生成可执行程序或共享库(so(shared object))。因此CMake的编译基本就两个步骤:
1. cmake
2. make
cmake 指向CMakeLists.txt所在的目录,例如cmake … 表示CMakeLists.txt在当前目录的上一级目录。cmake后会生成很多编译的中间文件以及makefile文件,所以一般建议新建一个新的目录,专门用来编译,例如
mkdir build
cd build
cmake ..
make
// make根据生成makefile文件,编译程序。
案例
cmake_minimum_required(VERSION 3.2)
project(six_calibration)
set(CMAKE_BUILD_TYPE "RelWithDebInfo")
set(3RD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../3rd)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin/debug)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin/debug)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin/debug/)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin/debug/)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
file(GLOB_RECURSE HEAD_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h ${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp)
file(GLOB_RECURSE SOURCES_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
set(SOURCE_FILE
${HEAD_LIST}
${SOURCES_LIST}
)
find_package(PCL 1.10.1)
add_definitions(${PCL_DEFINITIONS} -DGLOG_NO_ABBREVIATED_SEVERITIES)
link_directories(${PCL_LIBRARY_DIRS})
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/src
${3RD_PATH}/opencv3.4/include
${3RD_PATH}/cctag/include
${3RD_PATH}/spdlog/include
${3RD_PATH}/boost/include/boost-1_79/
${3RD_PATH}/eigen
${3RD_PATH}/nlohmann
${3RD_PATH}/ceres/include/
${3RD_PATH}/glog/include/
${3RD_PATH}/gflags/include/
${PCL_INCLUDE_DIRS}
)
if(WIN32)
set(3RD_LIB
${3RD_PATH}/opencv3.4/lib/opencv_world340.lib
${3RD_PATH}/cctag/lib/CCTag.lib
${3RD_PATH}/spdlog/lib/spdlog.lib
${3RD_PATH}/boost/lib/libboost_serialization-vc142-mt-x64-1_79.lib
${3RD_PATH}/open3d/lib/Open3D.lib
${3RD_PATH}/ceres/lib/ceres.lib
${3RD_PATH}/glog/lib/glog.lib
${3RD_PATH}/gflags/lib/gflags.lib
${PCL_LIBRARIES}
)
else()
endif()
add_compile_definitions(SIX_CALIBRATION_EXPORTS)
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILE})
target_link_libraries(${PROJECT_NAME}
${3RD_LIB}
${WIN32LIB}
${LINUXLIB}
)
file(GLOB_RECURSE TEST_HEAD_LIST ${CMAKE_CURRENT_SOURCE_DIR}/test/*.h ${CMAKE_CURRENT_SOURCE_DIR}/test/*.hpp)
file(GLOB_RECURSE TEST_SOURCES_LIST ${CMAKE_CURRENT_SOURCE_DIR}/test/*.c ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cpp)
set(TEST_SOURCE_FILE
${TEST_HEAD_LIST}
${TEST_SOURCES_LIST}
)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,10240000000")
add_executable(app_test ${TEST_SOURCE_FILE})
target_link_libraries(app_test
${3RD_LIB}
${WIN32LIB}
${LINUXLIB}
)
指定 cmake 的最小版本
cmake_minimum_required(VERSION 3.2)
指定了项目所需的最低CMake版本为3.2。这是一个必须的声明,以确保CMake版本符合要求。
设置项目名称
project(six_calibration)
设置项目的名称为"six_calibration",这个名称将在生成的构建系统中使用。
set(CMAKE_BUILD_TYPE "RelWithDebInfo")
设置构建类型为"RelWithDebInfo",这通常用于发布构建,包括调试信息以便于调试。
CMake设置输出目录
set(3RD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../3rd)
定义了一个名为3RD_PATH的变量,用于指定第三方依赖库的路径。
注:CMAKE_CURRENT_SOURCE_DIR是CMake中的另一个预定义变量,它用于表示当前处理的CMakeLists.txt文件所在的目录。
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin/debug)
设置可执行文件的输出路径为${PROJECT_SOURCE_DIR}/bin/debug。这将生成的可执行文件放在指定的目录下
。
注:PROJECT_SOURCE_DIR是CMake中的一个预定义变量,用于表示当前项目的根目录。CMake会自动设置这个变量,以指向包含CMakeLists.txt文件的目录,即你的项目根目录。
因此,如果你在CMakeLists.txt文件中使用了set(EXECUTABLE_OUTPUT_PATH P R O J E C T S O U R C E D I R / b i n / d e b u g ) ,那么 {PROJECT_SOURCE_DIR}/bin/debug),那么 PROJECTSOURCEDIR/bin/debug),那么{PROJECT_SOURCE_DIR}会被替换为你的项目根目录的路径,然后生成的可执行文件将会被放置在这个路径下的bin/debug目录中。
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin/debug)
设置库文件的输出路径为${PROJECT_SOURCE_DIR}/bin/debug。这将生成的库文件放在指定的目录下
。
eg: app_test.lib
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin/debug/)
设置调试构建的运行时输出目录。
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin/debug/)
设置调试构建的存档文件输出目录。
set(CMAKE_INCLUDE_CURRENT_DIR ON)
允许CMake包含当前源目录,这对于处理头文件很有用。
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
启用位置独立的代码生成,通常用于生成共享库。
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
设置C++标准为C++17,并要求编译器使用此标准。
指定编译包含的源文件
file(GLOB_RECURSE HEAD_LIST ...)
file(GLOB_RECURSE SOURCES_LIST ...)
查找指定的文件
使用Glob函数获取源文件和头文件的列表。
find_package(PCL 1.10.1)
查找PCL(Point Cloud Library)1.10.1版本。如果找到,将设置相关变量。
add_definitions(${PCL_DEFINITIONS} -DGLOG_NO_ABBREVIATED_SEVERITIES)
将PCL的定义添加到项目,并定义一个名为-DGLOG_NO_ABBREVIATED_SEVERITIES的宏。
设置链接库搜索目录
link_directories(${PCL_LIBRARY_DIRS})
添加PCL库目录到链接目录中。
设置包含的目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/src
${3RD_PATH}/opencv3.4/include
${3RD_PATH}/cctag/include
${3RD_PATH}/spdlog/include
${3RD_PATH}/boost/include/boost-1_79/
${3RD_PATH}/eigen
${3RD_PATH}/nlohmann
${3RD_PATH}/ceres/include/
${3RD_PATH}/glog/include/
${3RD_PATH}/gflags/include/
${PCL_INCLUDE_DIRS}
)
if(WIN32)
set(3RD_LIB
${3RD_PATH}/opencv3.4/lib/opencv_world340.lib
${3RD_PATH}/cctag/lib/CCTag.lib
${3RD_PATH}/spdlog/lib/spdlog.lib
${3RD_PATH}/boost/lib/libboost_serialization-vc142-mt-x64-1_79.lib
${3RD_PATH}/open3d/lib/Open3D.lib
${3RD_PATH}/ceres/lib/ceres.lib
${3RD_PATH}/glog/lib/glog.lib
${3RD_PATH}/gflags/lib/gflags.lib
${PCL_LIBRARIES}
)
else()
include_directories(…):添加包含目录,以便CMake知道在哪里查找头文件。
if(WIN32) 和 else():条件语句,检查操作系统是否为Windows。如果是,会设置一些Windows特定的变量。
eg:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}")
Linux 下还可以通过如下方式设置包含的目录
add_compile_definitions(SIX_CALIBRATION_EXPORTS)
添加一个编译宏,通常用于在代码中区分库的导出和导入。
指定编译包含的源文件
add_executable(demo demo.cpp) # 生成可执行文件
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILE})
创建一个共享库,库的名称是PROJECT_NAME,库的源文件是SOURCE_FILE。
SHARED: 生成动态库
STATIC:生成静态库
静态(函数)库
一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib 。
这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。
动态函数库
动态函数库的扩展名一般为(.so或.dll),这类函数库通常名为libxxx.so或xxx.dll 。
与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。
总结:综上,不能看出:
从产品化的角度,发布的算法库或功能库尽量使动态库,这样方便更新和升级,不必重新编译整个可执行文件,只需新版本动态库替换掉旧动态库即可。
从函数库集成的角度,若要将发布的所有子库(不止一个)集成为一个动态库向外提供接口,那么就需要将所有子库编译为静态库,这样所有子库就可以全部编译进目标动态库中,由最终的一个集成库向外提供功能。
注:
(1)在 Windows 下是:demo.exe,common.lib,common.dll
(2)在 Linux 下是:demo,libcommon.a,libcommon.so
target_link_libraries(${PROJECT_NAME} ...)
指定项目所依赖的库。
file(GLOB_RECURSE TEST_HEAD_LIST ...) 和 file(GLOB_RECURSE TEST_SOURCES_LIST ...)
文件列表获取,用于测试代码的头文件和源文件。
add_executable(app_test ${TEST_SOURCE_FILE})
创建一个可执行文件,名为"app_test",使用${TEST_SOURCE_FILE}中的源文件构建。
设置 target 需要链接的库
target_link_libraries(app_test ...)
eg:
target_link_libraries( # 目标库
demo
# 目标库需要链接的库
# log-lib 是上面 find_library 指定的变量名
${log-lib} )
在 Windows 下,系统会根据链接库目录,搜索xxx.lib 文件,Linux 下会搜索 xxx.so 或者 xxx.a 文件,如果都存在会优先链接动态库(so 后缀)。
target_link_libraries(demo libface.a) # 链接libface.a
target_link_libraries(demo libface.so) # 链接libface.so
指定链接动态库或静态库
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a)
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.so)
指定全路径
target_link_libraries(demo
${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a
boost_system.a
boost_thread
pthread)
指定链接多个库
其他
指定测试程序所依赖的库。
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,10240000000")
设置可执行文件的链接器标志,包括栈大小等。
文件路径
.
├── build
├── CMakeLists.txt
├── include
│ └── b.h
└── src
├── b.c
└── main.c
编写CMakeLists.txt
1 #1.cmake verson,指定cmake版本
2 cmake_minimum_required(VERSION 3.2)
3
4 #2.project name,指定项目的名称,一般和项目的文件夹名称对应
5 PROJECT(test_sqrt)
6
7 #3.head file path,头文件目录
8 INCLUDE_DIRECTORIES(
9 include
10 )
11
12 #4.source directory,源文件目录
13 AUX_SOURCE_DIRECTORY(src DIR_SRCS)
14
15 #5.set environment variable,设置环境变量,编译用到的源文件全部都要放到这里,否则编译能够通过,但是执行的时候会出现各种问题,比如"symbol lookup error xxxxx , undefined symbol"
16 SET(TEST_MATH
17 ${DIR_SRCS}
18 )
19
20 #6.add executable file,添加要编译的可执行文件
21 ADD_EXECUTABLE(${PROJECT_NAME} ${TEST_MATH})
22
23 #7.add link library,添加可执行文件所需要的库,比如我们用到了libm.so(命名规则:lib+name+.so),就添加该库的名称
24 TARGET_LINK_LIBRARIES(${PROJECT_NAME} m)