CMake是一个主要用于CPP的构建工具。CMake语言是平台无关的中间编译工具。同一个CMake编译规则在不同系统平台构建出不同的可执行构建文件。在Linux产生MakeFile,在Windows平台产生Visual Studio工程等。CMake旨在解决各平台的不同Make工具的产生的差异(比如GNU Make, QT的qmake,微软的nmake, BSD的pmake)。
其实除了CMake构建系统之外,CMake已经发展出一系列开发工具:CMake,CTest,CPack和CDash。
- CMake是负责构建软件的构建工具。
- CTest是一个测试驱动程序工具,用于运行回归测试。
- CPack是一种打包工具,用于为使用CMake构建的软件创建特定于平台的安装程序。
- CDash是一个Web应用程序,用于显示测试结果并执行连续的集成测试。
- 其他还有Doxygen和BullseyeCoverage
项目的通常做法是为Unix平台提供配置脚本和Makefile,为Windows提供Visual Studio项目文件。autoconf / libtool构建软件的方法不能满足跨平台的要求。
历史上曾经出现的1999年的VTK构建系统。该系统由Unix的配置脚本和pcmaker
Windows 的可执行文件组成。pcmaker
是一个C程序,可以读取Unix Makefile文件并为Windows创建NMake文件。
另一种是是gmake
针对Sun工作站上C ++计算机视觉环境。Sun工作站使用该imake
系统创建Makefile。但是,有时需要Windows端口时,gmake
才创建了系统。Unix编译器和Windows编译器均可与此gmake
基于此的系统一起使用。
这两个系统都存在严重缺陷:它们迫使Windows开发人员使用命令行。有经验的Windows开发人员更喜欢使用集成开发环境(IDE)。
CMake有两个阶段,配置和生成阶段。
配置阶段解析所有的输入变量,并存储在CMakeCache.txt这个文件。这个阶段解决了用户构建一个项目需要依赖的各种输入参数。
在项目的构建过程中都使用shell级别的环境变量。通常,项目具有指向根目录位置的PROJECT_ROOT环境变量。还有配置可选或外部程序包。要使构建正常进行,每次执行构建时都需要设置所有这些外部变量。所有CMakeFile在配置阶段解决了这个问题。
先来窥探下CMakeCache.txt的构成,CmakeCache.txt由两部分构成:External Cache Entries和Internal Cache Entries。而CMakeCache.txt是由解析器Parser生成。解析器的匹配器找到各种token。CMakeLists也可以解析外部的CMake语法,他是由“include” 或者“add_subdirectory”包含进来,两者的区别后面会说到。
解析完这些变量,cmake在内存中有了项目(可执行程序、库、用户自定义Command)的构建表达方法。在代码中一个target用cmTarget对象表示,所有的cmTarget构成了cmMakefile对象。
在生成阶段,cmake使用了一套语法解析系统,关键的类图如下。cmMakefile对象存错了CMakeLists.txt的所有输入变量。解析器使用了lex/yacc语法解析器,执行构建动作。cmCommand定义了命令的执行动作,并且该动作的注释在代码也有注释。这些关键类 是抽象类,CMake的跨平台实现依赖于这些类的平台实现类。
CMake在使用IDE的平台不生成依赖,这些依赖由IDE自己完成。在Unix系统,CMake做了依赖管理,并把这些信息写在depend.make,flags.make, build.make,DependInfo.cake。当这些文件有变化,都会从cmake的重新构建。
depend.make和DependInfo.make:所有object的依赖关系。DependInfo.cmake保存了语言和对象文件的关系。
flags.make保存了编译选项,如果编译选项改变了,也会触发重建构建
最后这些信息都会汇总成build.make
CMakeLists.txt定义了所有编译规则的入口。CMakeLists的常用编译指令按照目的分类有:
我们联想从最简单的编译规则说起:
gcc -Wall -std=c++11 -DMY_MACRO -I/home/lib [-Ldir] -llibname main.c -o main
比如gcc 这里的-Wall是编译选项,-DMY_MACRO定义了MY_MACRO宏,-L指库的搜索路径,-l指链接libname库,源文件是main.c,最终生成的二进制可执行文件是main
那么怎么用CMake表示这个规则。
target_compile_features(target PRIVATE|PUBLIC|INTERFACE feature1 [feature2 ...])
PRIVATE的意思是这个target的编译选项只对该target有效,如果需要对引用该target的上级target也有效,那么这里需要用PUBLIC。
样例:
target_compile_features(main PRIVATE “-Wall”)
set_target_properties(main PROPERITES
COMPILE_FLAGS "-Wall"
)
target_compile_features(mylib PUBLIC cxx_std_11)
还有个target_compile_option()是什么区别
另外提一下,这里C++在这里是CXX? 因为涉及到不同平台下C++程序的后缀名不一样,在Windows下我们常用的就是一个.cpp扩展名,还有gcc一般用c.cc.cxx 等等都是C++文件的扩展名。
有些c++就是直接用语言的名字命名的扩展名,但有些系统可能不支持在文件名里放入加号"+",或许这里用cxx的x有点像+,当时设计意图可能是这边吧。
编译命令可以归结为以下3个大类:
SET(CMAKE_CXX_STANDARD 14):为什么是CXX
如果开启了CXX_VARIADIC_TEMPLATES
#if Foo_COMPILER_CXX_VARIADIC_TEMPLATES
#else
#endif
让CMake找到我的头文件, include_directories(/home/include)。常见的也有这样写,把工程的include文件夹加到包含路径。
include_directories(${CMAKE_CURRENT_LIST_DIR}/include),
CMAKE_CURRENT_LIST_DIR这个变量,它表示当前CMakeLists所在的路径.或者PROJECT_SOURCE_DIR,这个命令的原型是
命令: include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
作用是把dir1, [dir2 …]这(些)个路径添加到当前CMakeLists及其子CMakeLists的头文件包含路径中;
AFTER 或者 BEFORE 指定了要添加的路径是添加到原有包含列表之前或之后
若指定 SYSTEM 参数,则把被包含的路径当做系统包含路径来处理
如果需要递归include文件夹及子文件夹的所有目录,用
add_subdirectory()
那target_inlucde_directories()是指什么,库的所有者都可以使用
外部的target
#include(TARGET),它会去子文件夹cmake/TARGET文件夹,搜索TARGET.cmake的文件。
aux_source_directory(./src ${hello_src})
作用: 把当前路径下src目录下的所有源文件路径放到变量hello_src中
命令:aux_source_directory(<dir> <variable>)
作用:查找dir路径下的所有源文件,保存到variable变量中.
上面的例子中,hello_src是一个自定义变量,在执行了aux_source_directory(./src ${hello_src})之后,我就可以像这样来添加一个可执行文件:add_executable(hello ${hello_src}), 意思是用hello_src里面的所有源文件来构建hello可执行程序, 不用手动列出src目录下的所有源文件了。
值得注意的是:aux_source_directory 不会递归包含子目录,仅包含指定的dir目录
CMake官方不建议用aux_source_directory及类似命令(file(GLOB_RECURSE …))搜索源文件。因为这样子文件夹的变化不容易被感知到,从而无法触发重新构建。比如被搜索的路径下添加源文件,此时没有修改CMakeLists脚本,但是CMakeLists并不需要(没有)变化,构建系统无法察觉到新加的文件,除非手动重新运行cmake,否则新添加的文件就不会被编译到项目结果中。
link_directories(${CMAKE_CURRENT_LIST_DIR}/lib)
link_directories(directory1 directory2 ...)和include_directories()类似他,添加库包含路径。
target_link_libraries(${PROJECT_NAME} util)
命令:target_link_libraries(<target> [item1 [item2 [...]]] [[debug|optimized|general] <item>] ...)
这个target需要链接util这个库,会优先搜索libutil.a(windows上就是util.lib), 如果没有就搜索libutil.so(util.dll, util.dylib)’
类似于与pkg-config去文件夹找*.pc,cmake也提供了find_package(),它会去cmake安装目录module文件夹执行Find<Package>.cmake
Target包括3种: executable、 library、自定义command
指令分别为
add_custom_command()
add_library(archive archive.cpp zip.cpp lzma.cpp)
add_executable(zipapp zipapp.cpp)
链接库和最终target:target_link_libraries(zipapp archive)
message(STATUS “my custom debug info”)
FILE()
foreach()
endforeach()
macro()
endmacro()
设置要求版本>=3.5:CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
CMAKE_MODULE_PATH:
什么是工程MODULE,多个工程连接
编译选项:
SET(CMAKE_CXX_STANDARD 14):为什么是CXX
#include(TARGET),它会去子文件夹cmake/搜索TARGET.cmake的文件。也可能去cmake的安装目录下搜索。
3.7.8、工程包名字
PROJECT(output_binary_name CXX)
ExternalProject在构建时从另一个项目填充内容。这意味着在构建主项目之前,本地没有其他项目的库。首先需要add_dependencies()声明,ExternalProject才会下载,配置或构建。最主要外部下载引用是 ExternalProject_Add,功能很强大,支持不同的地址去获取依赖,可以是打包文件的 URL,比如 github 上的某个项目的 tag,或者像 boost 这种,在官网提供的下载链接,也可以直接是 GIT_REPOSITORY,一般建议直接使用打包的 tag,因为比较快,而且有固定的 tag,比较好做版本管理,但是有些项目引用了外部项目需要执行 git submodule update --init,这种就比较适合用 git 地址,会自动下载依赖模块
一个ExternalProject_ADD的例子如下:
ExternalProject_ADD(
#--External-project-name------
antlr4cpp
#--Depend-on-antrl-tool-----------
# DEPENDS antlrtool
#--Core-directories-----------
# PREFIX ${ANTLR4CPP_EXTERNAL_ROOT}
PREFIX ${ANTLR4CPP_LOCAL_ROOT}
#--Download step--------------
# GIT_REPOSITORY ${ANTLR4CPP_EXTERNAL_REPO}
URL ${ANTLR4CPP_LOCAL_REPO}
# GIT_TAG ${ANTLR4CPP_EXTERNAL_TAG}
TIMEOUT 10
LOG_DOWNLOAD ON
#--Update step----------
UPDATE_COMMAND ${GIT_EXECUTABLE} pull
#--Patch step----------
# PATCH_COMMAND sh -c "cp <SOURCE_DIR>/scripts/CMakeLists.txt <SOURCE_DIR>"
#--Configure step-------------
CONFIGURE_COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Release -DANTLR4CPP_JAR_LOCATION=${ANTLR4CPP_JAR_LOCATION} -DBUILD_SHARED_LIBS=ON -BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_SOURCE_DIR:PATH=<SOURCE_DIR>/runtime/Cpp <SOURCE_DIR>/runtime/Cpp
LOG_CONFIGURE ON
#--Build step-----------------
# BUILD_COMMAND ${CMAKE_MAKE_PROGRAM}
LOG_BUILD ON
#--Install step---------------
# INSTALL_COMMAND ""
# INSTALL_DIR ${CMAKE_BINARY_DIR}/
#--Install step---------------
# INSTALL_COMMAND ""
)
下载完之后编译这个过程,基本不需要额外的配置,会自动编译,也许会按照个人习惯设置一个编译后的 install 目录,可以通过 CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${DMP_CLIENT_SOURCE_DIR}/third/gtest/build 设置 cmake 的参数来实现。
这些变量和指令不好记,怎么快速记忆。
--trace-expand
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。