Cmake使用
一 ,Cmake 初级使用
- 项目主目录中的 CMakeLists.txt
1 PROJECT(main) #项目名称
2 CMAKE_MINIMUM_REQUIRED(VERSION 2.6) #版本号
3 ADD_SUBDIRECTORY( src ) #项目中包含子目录src
4 AUX_SOURCE_DIRECTORY(. DIR_SRCS) #将当前目录下源文件名称存入 DIR_SRCE变量中
5 ADD_EXECUTABLE(main ${DIR_SRCS} ) #可执行文件名称
6 TARGET_LINK_LIBRARIES( main Test ) #生成可执行文件需要链接的库(Test)
- 子目录src中的 CmakeLists.txt,生成连接库
1 AUX_SOURCE_DIRECTORY(. DIR_TEST1_SRCS)
2 ADD_LIBRARY ( Test ${DIR_TEST1_SRCS}) #将源文件编译为共享库
二 ,Cmake中级使用—在工程中连接其他库
在开发过程会用到一些函数库,这些函数库在不同的系统中安装的位置可能不同,编译的时候需要首先找到这些软件包的头文件以及链接库所在的目录以便生成编译选项。例如一个需要使用博克利数据库项目,需要头文件db_cxx.h 和链接库 libdb_cxx.so ,现在该项目中有一个源代码文件 main.cpp ,放在项目的根目录中。
- 在项目的根目录中创建目录 cmake/modules/ ,在 cmake/modules/ 下创建文件 Findlibdb_cxx.cmake,文件 Findlibdb_cxx.cmake 的命名要符合规范: FindlibNAME.cmake ,其中NAME 是函数库的名称。Findlibdb_cxx.cmake 的语法与 CMakeLists.txt 相同。
# MESSAGE 会将参数的内容输出到终端。
01 MESSAGE(STATUS "Using bundled Findlibdb.cmake...")
# 原型find_path(<VAR> name1 [path1 path2 ...]) ,该命令在参数 path* 指示的目录中
# 查找文件 name1 并将查找到的路径保存在变量 VAR 中。
02 FIND_PATH
03 (
04 LIBDB_CXX_INCLUDE_DIR
05 db_cxx.h
06 /usr/include/
07 /usr/local/include/
08 )
09# 命令 FIND_LIBRARY 同 FIND_PATH 类似,用于查找链接库并将结果保存在变量中。
10 FIND_LIBRARY(
11 LIBDB_CXX_LIBRARIES NAMES
12 db_cxx
13 PATHS /usr/lib/ /usr/local/lib/
14 )
- 项目的根目录中的 CmakeLists.txt
01 PROJECT(main)
02 CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
03 SET(CMAKE_SOURCE_DIR .)
04 SET(CMAKE_MODULE_PATH ${CMAKE_ROOT}/Modules ${CMAKE_SOURCE_DIR}/cmake/modules) # 设置模板目录
05 AUX_SOURCE_DIRECTORY(. DIR_SRCS)
06 ADD_EXECUTABLE(main ${DIR_SRCS})
# 使用命令 FIND_PACKAGE 进行查找,这条命令执行后
# CMake 会到变量 CMAKE_MODULE_PATH 指示的目录中
# 查找文件 Findlibdb_cxx.cmake 并执行
07 FIND_PACKAGE( libdb_cxx REQUIRED)
08 MARK_AS_ADVANCED(
09 LIBDB_CXX_INCLUDE_DIR
10 LIBDB_CXX_LIBRARIES
11 )
# 条件判断语句,表示如果 LIBDB_CXX_INCLUDE_DIR 和
# LIBDB_CXX_LIBRARIES 都已经被赋值,则设置编译时
# LIBDB_CXX_INCLUDE_DIR 寻找头文件并且设置可执行文件 main
# 需要与链接库 LIBDB_CXX_LIBRARIES 进行连接
12 IF (LIBDB_CXX_INCLUDE_DIR AND LIBDB_CXX_LIBRARIES)
13 MESSAGE(STATUS "Found libdb libraries")
14 INCLUDE_DIRECTORIES(${LIBDB_CXX_INCLUDE_DIR})
15 MESSAGE( ${LIBDB_CXX_LIBRARIES} )
16 TARGET_LINK_LIBRARIES(main ${LIBDB_CXX_LIBRARIES}17 )
18 ENDIF (LIBDB_CXX_INCLUDE_DIR AND LIBDB_CXX_LIBRARIES)
- 完成 Findlibdb_cxx.cmake 和 CMakeList.txt 的编写后在项目的根目录依次执行 “cmake . ” 和 “make ” 可以进行编译
三 ,Cmake高级使用—脚本搭配与删除
- 项目的根目录中的 CmakeLists.txt
cmake_minimum_required(VERSION 3.6.1)
# 版本号
project(ml_deviceinfo_cpp VERSION 1.0.0.0 LANGUAGES CXX)
# 项目名称
# CMake中的set用于给一般变量,缓存变量,环境变量赋值。
#
SET( CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING
"Choose the type of build, options are:
None Debug Release RelWithDebInfo Asan Pprof."
FORCE )
message("* Current build type is : ${CMAKE_BUILD_TYPE}")
# 置C++ 编译选项,也可以通过指令ADD_DEFINITIONS
SET( CMAKE_CXX_FLAGS_ASAN "-O2 -g -fsanitize=address -fno-omit-
frame-pointer" CACHE STRING
"Flags used by the C++ compiler during asan builds."
FORCE )
# C编译权限
SET( CMAKE_C_FLAGS_ASAN "-O2 -g -fsanitize=address -fno-omit-
frame-pointer" CACHE STRING
"Flags used by the C compiler during asan builds."
FORCE )
# 可执行文件
SET( CMAKE_EXE_LINKER_FLAGS_ASAN
"-static-libasan" CACHE STRING
"Flags used for linking binaries during asan builds."
FORCE )
# 共享库
SET( CMAKE_SHARED_LINKER_FLAGS_ASAN
"-static-libasan" CACHE STRING
"Flags used for linking shared libraries during asan builds."
FORCE )
#标记缓存变量为advanced
#Mark the named cached variables as advanced.
#An advanced variable will not be displayed in any of the cmake GUIs
#unless the show advanced option is on.
#If CLEAR is the first argument advanced variables are
#changed back to unadvanced. If FORCE is the first argument,
#then the variable is made advanced. If neither FORCE nor
CLEAR is specified,
#new values will be marked as advanced,
#but if the variable already has an advanced/non-advanced state,
# it will not be changed.
# It does nothing in script mode.
MARK_AS_ADVANCED(
CMAKE_CXX_FLAGS_ASAN
CMAKE_C_FLAGS_ASAN
CMAKE_EXE_LINKER_FLAGS_ASAN
CMAKE_SHARED_LINKER_FLAGS_ASAN )
# 用于载入预先的cmake模块
include(cmake/deps_check_boost.cmake)
include(cmake/deps_check_datastax_cpp-driver.cmake)
include(cmake/ml_deviceinfo_cpp.cmake)
include(cmake/ml_deviceinfo_cpp_export.cmake)
# option 提供选项让用户选择是 ON 或者 OFF ,
#如果没有提供初始化值,使用OFF。也就是说默认的值是OFF。但是请注意:
#这个option命令和你本地是否存在编译缓存的关系很大。
# 所以,如果你有关于 option 的改变,那么请你务必清理 CMakeCache.txt
#和 CMakeFiles文件夹。
option (ML_DEVICEINFO_CPP_ENABLE_TESTS "If OFF, used to
disable testing altogether." OFF)
if(${ML_DEVICEINFO_CPP_ENABLE_TESTS})
include(cmake/ml_device_info_test.cmake)
endif()
- cmake目录下下ml_deviceinfo_cpp.cmake
add_library(ml_deviceinfo_cpp
src/ml_cass_client.cpp
src/ml_deviceinfo_client.cpp
src/histogram.cpp
)
target_include_directories(ml_deviceinfo_cpp PRIVATE
${PROJECT_SOURCE_DIR}/include
)
target_include_directories(ml_deviceinfo_cpp INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>
)
if(NOT "${Boost_INCLUDE_DIRS}" STREQUAL "")
target_include_directories(ml_deviceinfo_cpp PRIVATE
${Boost_INCLUDE_DIRS}
)
target_include_directories(ml_deviceinfo_cpp INTERFACE
${Boost_INCLUDE_DIRS}
)
endif()
target_link_libraries(ml_deviceinfo_cpp PUBLIC
cassandra-cpp-driver::cassandra-cpp-driver
libuv::libuv
pthread
)
target_compile_options(ml_deviceinfo_cpp PUBLIC
-D_GLIBCXX_USE_CXX11_ABI=0
-fPIC
-fvisibility=hidden
${Cassandra_CFLAGS}
)
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
target_compile_options(ml_deviceinfo_cpp PRIVATE
-std=c++11
)
else()
target_compile_options(ml_deviceinfo_cpp PRIVATE
-std=c++17
)
endif()
add_library(ml::deviceinfo_cpp ALIAS ml_deviceinfo_cpp)
set_target_properties(ml_deviceinfo_cpp PROPERTIES EXPORT_NAME deviceinfo_cpp)
install(TARGETS ml_deviceinfo_cpp
EXPORT ml_deviceinfo_cpp_targets
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
- cmake目录下下的ml_device_info_test.cmake
# 增加二进制可执行文件
add_executable(ml_cass_client_unittest
src/unittest/ml_cass_client_test.cpp
)
add_executable(ml_deviceinfo_client_unittest
src/unittest/ml_deviceinfo_cpp_test.cpp
)
add_executable(ml_deviceinfo_client_bench
src/benchmark/ml_deviceinfo_cpp_bench.cpp
)
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
target_compile_options(ml_cass_client_unittest PRIVATE -std=c++11)
target_compile_options(ml_deviceinfo_client_unittest PRIVATE -std=c++11)
target_compile_options(ml_deviceinfo_client_bench PRIVATE -std=c++11)
else()
target_compile_options(ml_cass_client_unittest PRIVATE -std=c++17)
target_compile_options(ml_deviceinfo_client_unittest PRIVATE -std=c++17)
target_compile_options(ml_deviceinfo_client_bench PRIVATE -std=c++17)
endif()
#
include(${PROJECT_SOURCE_DIR}/cmake/deps_check_gtest.cmake)
target_include_directories(ml_cass_client_unittest PRIVATE
${GTEST_INCLUDE_DIRS}
${PROJECT_SOURCE_DIR}/include
)
target_link_libraries(ml_cass_client_unittest PRIVATE
ml::deviceinfo_cpp
${GTEST_LIBRARIES}
)
target_include_directories(ml_deviceinfo_client_unittest PRIVATE
${GTEST_INCLUDE_DIRS}
${PROJECT_SOURCE_DIR}/include
)
target_link_libraries(ml_deviceinfo_client_unittest PRIVATE
ml::deviceinfo_cpp
${GTEST_LIBRARIES}
)
target_include_directories(ml_deviceinfo_client_bench PRIVATE
${PROJECT_SOURCE_DIR}/include
)
target_link_libraries(ml_deviceinfo_client_bench PRIVATE
ml::deviceinfo_cpp
)
file(COPY ${PROJECT_SOURCE_DIR}/src/unittest/populate_db.cql
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/populate_db.cql)
- 运行脚本,会生成build目录对可执行文件进行存储,删除时rm -R build
set -e
# pushd:切换到作为参数的目录,并把原目录和当前目录压入到一个虚拟的堆栈中
# 如果不指定参数,则会回到前一个目录,并把堆栈中最近的两个目录作交换
pushd $(dirname ${BASH_SOURCE[0]})
if [[ ! -d build ]]; then
mkdir build
fi
cd build
#
#-DCMAKE_BUILD_TYPE=
#选择编译模式
#Release, RelWithDebInfo, Debug, etc
#-DCMAKE_INSTALL_PREFIX=
#指定安装路径。UNIX 默认安装路径 /usr/local
#-DBUILD_SHARED_LIBS=
#可以选择 ON 或者 OFF 选择默认是否编译成共享库
#调试方法
#可以使用 --trace 来查看详细的CMake configure output. 它会用输出CMake执行中的每一行。 在 3.7 版本后, 可以只输出制定文件的信息 --trace-source="filename"
#
cmake3 ../ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DDEPS_DIR=/data/code/ml-platform-thirdparty \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DML_DEVICEINFO_CPP_ENABLE_TESTS=ON
#-DPROTOBUF_PREFIX=/data/code/ml-platform-thirdparty/protobuf3
#
make #make后执行Cmakelist.txt
# 外部构建,将中间文件存放在build目录中,保持代码整洁
popd
if [[ $? -eq 0 ]];then
echo "OK"
else
echo "Failed"
fi
四, 语法指令汇总
1. PROJECT
PROJECT(projectname [CXX] [C] [Java])
你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。
这个指令隐式的定义了两个cmake 变量: _BINARY_DIR _SOURCE_DIR,这里就是HELLO_BINARY_DIR 和HELLO_SOURCE_DIR( CMakeLists.txt 中两个MESSAGE 指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所在路径/backup/cmake/t1 ,后面我们会讲到外部编译,两者所指代的内容会有所不同。
2. SET
SET(VAR [VALUE] [CACHE TYPE DOCSTRING[FORCE]])
初期阶段,你只需要了解SET指令可以用来显式的定义变量即可。比如我们用到的是SET(SRC_LIST main.c),如果有多个源文件,也可以定义成:SET(SRC_LIST main.c t1.c t2.c)
3. MESSAGE
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] “message to display”…) 这个指令用于向终端输出用户定义的信息,包含了三种类型: SEND_ERROR,产生错误,生成过程被跳过。 SATUS,输出前缀为—的信息。
FATAL_ERROR,立即终止所有 cmake 过程。
我们在这里使用的是 STATUS 信息输出,演示了由 PROJECT 指令定义的两个隐式变量
HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR。
FATAL_ERROR,立即终止所有cmake 过程.
4. ADD_EXECUTABLE
ADD_EXECUTABLE(hello {SRC_LIST})定义了这个工程会生成一个文件名为hello的可执行文件,相关的源文件是SRC_LIST 中定义的源文件列表,本例中你也可以直接写成ADD_EXECUTABLE(hello main.c) 。
在本例我们使用{}来引用变量,这是cmake的变量应用方式,但是,有一些例外,比如在IF控制语句,变量是直接使用变量名引用,而不需要{}去应用变量,其实IF会去判断名为${}所代表的值的变量,那当然是不存在的了。
将本例改写成一个最简化的CMakeLists.txt:
PROJECT(HELLO)
ADD_EXECUTABLE(hello main.c)
ADD_LIBRARY ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL]
source1 source2 … sourceN) 你不需要写全libhello.so,只需要填写hello 即可,cmake 系统会自动为你生成libhello.X
类型有三种: SHARED,动态库STATIC,静态库MODULE ,在使用dyld的系统有效,如果不支持dyld,则被当作SHARED 对待。