Cmake全称Cross Platform Make,起初为了跨平台需求,而后不断完善并广泛使用。优势是跨平台, 支持Linux, Mac和Windows等不同操作系统。
一.Cmake与其他编译工具的对比
gcc/g++是很常见的编译工具,是由GNU开发的编程语言译器, 主要用于C/C++等语言的开发,当项目简单,可以用gcc/g++编译目标和项目,但比较复杂时, 只用gcc组织编译架构会变得极其困难。Makefile是有条理的gcc编译命令的文件,利用make工具来执行Makefile文件的编译指令,当程序简单时, 可以手写Makefile,当程序复杂时, 一般利用CMake来自动生成Makefile。因而Cmake类似Make工具功能,用来“读取”并执行CMakeLists.txt文件的语句, 最终生成Makefile文件。Cmake语言开发相对简单,易于理解。
二.Cmake的常用指令
Cmake类似Make工具功能,用来读取并执行CMakeLists.txt,这个文件是cmake的构建定义文件,文件名是大小写相关的,如果工程存在多个目录,需要确保每个要管理的目录都存在一个CMakeLists.txt。CMakeLists.txt有一些的常用指令,笔者总结了自己项目过程里最常用的指令,不常用的没有列出。主要${}是引用变量,这是cmake的变量应用方式,但是,有一些例外,比如在IF控制语句,变量是直接使用变量名引用,而不需要${}。如果使用了${}去应用变量,其实IF会去判断名为${}所代表的值的变量,那当然是不存在的了。
- project(projectname [CXX] [C] [Java])
用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的。
- set(<variable> <value>)
set指令可以用来显式的定义变量。
- message([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"...)
这个指令用于向终端输出用户定义的信息,包含了三种类型:
SEND_ERROR,产生错误,生成过程被跳过。
SATUS,输出前缀为-- 的信息(以简洁的方式显示用户感兴趣的信息)。
FATAL_ERROR,立即终止所有cmake过程。
如message(STATUS "PROJECT_NAME: " ${PROJECT_NAME})最终能看到-- PROJECT_NAME: demo这样的提示信息。
也可以直接这样:message("OpenCV: ${OpenCV_LIBS}")就直接显示信息。
- find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE] [REQUIRED [[COMPONENTS] [components...]] [OPTIONAL_COMPONENTS components...])
主要是寻找和加载外部项目。如果PackageName找到了,PackageName-found会显出,当没有找到时,默认显示PackageName-not found。通过模式的选择,可以处理在没有找到包时的解决方案。QUIET:不显示有用信息,REQUIRED:报错。
如找oepncv库可以这样用:find_package(OpenCV REQUIRED)
- find_path (<VAR> name0|NAMES name1 [path1 path2 ...])
用以寻找包含着name1文件的目录,如果找到了结果存储在VAR,没有找到结果结果是VAR-not found。成功时,变量被清除find_path再次搜索,没有成功,fin_path再次以相同的变量被调用时搜索。
- add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个指令用于向当前工程添加将被编译的存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。指明CMakeLists.txt所在目录下包含了一个子目录source_dir,这样source_dir下的源文件和CMakeLists.txt等也会被处理。EXCLUDE_FROM_ALL参数的含义是将这个目录从编译过程中排除,比如,工程的example,可能就需要工程构建完成后,再进入example目录单独进行构建。
- INSTALL
用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。 常用的如OpenCV一般情况下安装到系统目录,即/usr/lib, /usr/bin和/usr/include [Ubuntu系统]
三.与静态库和共享库相关的指令
静态库:链接阶段, 库中目标文件所含的所有将被程序使用的函数的机器码, 被copy到最终的可执行文件中,因此对应的链接方式称为静态链接。静态库对函数库的链接是放在编译时期完成的,程序在运行时与函数库再无瓜葛, 移植方便,所以运行效率相对快,但缺点是占用磁盘和内存空间,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
动态库(共享库):程序编译时并不会被连接到目标代码中, 而是在程序运行时才被载入。所以静态库是牺牲了空间效率, 换取了时间效率;共享库是牺牲了时间效率, 换取了空间效率;
首先看看如何建立静态库和共享库。
- add_library (name dir)
用在目录dir下的源文件生成一个名为name的静态链接库:libname.a (win下是产生name.lib)注意会自动在名字前加lib。
- add_library(libname SHARED source1 source2 ... sourceN)
生成一个名为name的动态库(扩展名为.so,win下是.dll)
接下来看看如何使用外部共享库和头文件。
- add_executable(hello ${SRC_LIST})
定义了这个工程会生成一个文件名为hello的可执行文件,相关的源文件是SRC_LIST中定义的源文件列表, 例如add_executable(hello main.cpp)。一般都要包含main.hpp头文件。
- target_link_libraries(exec library1<debug | optimized> library2...)
表示为可执行程序exec添加需要链接的静态库或共享库。如TARGET_LINK_LIBRARIES(main libhello.so)再如TARGET_LINK_LIBRARIES(main libhello.a)。
- include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面。
- target_include_directories(<target>[SYSTEM][BEFORE]<INTERFACE|PUBLIC|PRIVATE> [items])
设置include文件查找的目录,具体包含头文件应用形式,安装位置等。
- ADD_CUSTOM_COMMAND/TARGET
[COMMAND] : 为工程添加一条自定义的构建规则。[TARGET] : 用于给指定名称的目标执行指定的命令,该目标没有输出文件,并始终被构建。
如在cmakelist.txt里写如下函数就自定义了一个copy共享库的自定义命令。
function(cpy_dlls_to_target targe)
foreach (DFILE ${ARGN})
message("dll: " ${DFILE})
add_custom_command(TARGET ${targe} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${DFILE}"
$<TARGET_FILE_DIR:${targe}>)
endforeach ()
endfunction()
四.简单的案例
这里举以调opencv库来实现自己的程序main.cpp为例串联下常用指令。那么项目目录下CMakeLists.txt这样写。
cmake_minimum_required(VERSION 3.6)
set(PROJECT_NAME "demo")
project(${PROJECT_NAME})
set(CMAKE_CXX_STANDARD 11)
set(OpenCV_INCLUDE_DIR "D:/opencv3.3/opencv/build/include/")
set(OpenCV_LIBS "D:/opencv3.3/opencv/build/x64/vc14/lib/opencv_world330d.lib")
set(OpenCV_DLL "D:/opencv3.3/opencv/build/x64/vc14/bin/opencv_world330d.dll")
add_executable(${PROJECT_NAME} main.cpp)
# 链接OpenCV库文件
target_include_directories(${PROJECT_NAME} PUBLIC ${OpenCV_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS})
当然假如说自己写的源代码太多,就不能都以add_executable(${PROJECT_NAME} main.cpp xxx.cpp)这样的方式了,而是应该把自己实现的某些程序当成库。这里假设自己实现的库名字叫myfunctions,创了一个名为myfunctions的文件夹,此时有好几个用于实现某种想要功能的源代码在这个文件夹下,我们可以在myfunctions下也写一个CMakeLists.txt,
生成一个自建的库:add_library(myfunctions xxx.cpp xxxx.cpp)
当然自建库相当于就是开发一个sdk,自然可以使用外部的库如opencv等库,CMakeLists.txt可以写很多功能。完成了自建库后,在项目目录下的CMakeLists.txt里调用自建库。
经过修改,再加一个自动复制opencv的dll到clion创建项目的所需目录下(因为如果使用clion开发,必须要有dll文件在文件夹cmake-build-release或cmake-build-debug下)。假如自己的项目有一个文件夹叫src,存放了许多自己的项目代码,那么就需要aux_source_directory(./src DIR_my),然后add_executable(${PROJECT_NAME} ${DIR_SRCS})。完善的项目目录下的CMakeLists.txt这么写:
cmake_minimum_required(VERSION 3.6)
set(PROJECT_NAME "demo")
project(${PROJECT_NAME})
MESSAGE(STATUS "PROJECT_NAME: " ${PROJECT_NAME})
set(CMAKE_CXX_STANDARD 11)
# Check platforms
if (CMAKE_HOST_WIN32)
set(WINDOWS 1)
MESSAGE(STATUS "PLATFORM: WINDOWS")
elseif (CMAKE_HOST_APPLE)
set(MACOS 1)
MESSAGE(STATUS "PLATFORM: MACOS")
elseif (CMAKE_HOST_UNIX)
set(LINUX 1)
MESSAGE(STATUS "PLATFORM: LINUX")
endif ()
set(OpenCV_INCLUDE_DIR "D:/opencv3.3/opencv/build/include/")
set(OpenCV_LIBS "D:/opencv3.3/opencv/build/x64/vc14/lib/opencv_world330d.lib")
set(OpenCV_DLL "D:/opencv3.3/opencv/build/x64/vc14/bin/opencv_world330d.dll")
function(cpy_dlls_to_target targe)
foreach (DFILE ${ARGN})
message("dll: " ${DFILE})
add_custom_command(TARGET ${targe} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${DFILE}"
$<TARGET_FILE_DIR:${targe}>)
endforeach ()
endfunction()
aux_source_directory(./src DIR_my)
include_directories ("${PROJECT_SOURCE_DIR}/myfunctions")
add_subdirectory (myfunctions)
set (EXTRA_LIBS ${EXTRA_LIBS} myfunctions)
if (WINDOWS)
cpy_dlls_to_target(${PROJECT_NAME} "${OpenCV_DLL}")
endif (WINDOWS)
add_executable(${PROJECT_NAME} ${DIR_SRCS})
# 链接库文件
target_include_directories(${PROJECT_NAME} PUBLIC ${OpenCV_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS} ${EXTRA_LIBS})
参考:https://www.cnblogs.com/skynet/p/3372855.html
https://www.cnblogs.com/52php/p/5681755.html