CMake is great. don't waste time on other C++ build tools, seriously.html
CMake
是一款工程构建工具,相似的工具还有autotool
,qmake
,Scons
等等。ios
具体见:
为何选用cmakec++
随着功能的不断增长和复杂,咱们写的C++程序也不可能再像Helloworld.cpp
那样只有一个源文件了,整个程序工程中,会有不少的源文件与库文件,如何将他们组合,编译成可执行的程序,就是CMake做为工程构建工具的做用:它须要告诉计算机,整个复杂工程的文件之间有怎么样的关系。git
这个过程经过一个叫CMakeList.txt
的文件来进行。github
cmake_minimum_required(VERSION 2.6) project(itest) # C++标准 set(CMAKE_CXX_STANDARD 11) # 指定参与编译的源文件 add_executable(itest src/main.cpp src/cal/Calculator.cpp src/cal/Calculator.h) # 指定安装路径,make install 时运用 install (TARGETS itest DESTINATION bin) install(DIRECTORY src/ DESTINATION include/itest FILES_MATCHING PATTERN "*.h") # 设置不一样build类别时的编译参数 #set(CMAKE_BUILD_TYPE "Debug") set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb") set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
根据CMake 入门实战 - Hahack的实例来学习记录。编程
原文C语言Demo源码出自《CMake入门实战》源码 - github, wzpan,本文改写为C++,代码地址 - github。bash
Linux
平台使用CMake
生成Makefile
并编译的流程:CMake
配置文件CMakeList.txt
cmake ${CMAKELIST_PATH}
生成Makefile
,${CMAKELIST_PATH}
是CMakeList.txt
所在的目录。make
进行编译。如下面的Demo_1
- github的实例来学习程序为例子,项目中的一个源文件main.cpp
以下:ide
/* * power - Calculate the power of number. * @param base: Base value. * @param exponent: Exponent value. * * @return base raised to the power exponent. */ #include <iostream> using namespace std; double power(double base, int exponent) { int result = base; int i; if (exponent == 0) { return 1; } for(i = 1; i < exponent; ++i){ result = result * base; } return result; } int main(int argc, char *argv[]) { if (argc < 3){ // printf("Usage: %s base exponent \n", argv[0]); cout << "Usage: " << argv[0] << "base exponent" <<endl; return 1; } double base = atof(argv[1]); int exponent = atoi(argv[2]); double result = power(base, exponent); // printf("%g ^ %d is %g\n", base, exponent, result); cout << base << "^" << exponent << " is " << result <<endl; return 0; }
首先编写CMakeList.txt
,并保存在与main.cpp
文件同一个目录下:函数
~/Demo_1 | +--main.cpp +--CMakeList.txt
一个最基本的CMakeList.txt
是这样的,在这个例子中,咱们只须要告诉计算机,咱们要使用main.cpp
文件,来编译一个名为Demo
的可执行文件:工具
# CMake 最低版本号要求 cmake_minimum_required (VERSION 2.8) # 项目信息 project (Demo_1) # 指定生成目标 add_executable(Demo main.cpp)
CMake
中,#
后面的内容为注释,命令不区分大小写,参数之间用空格分隔,基本格式为:
commandName(arg1 arg2 ...)
上面的CMakeList.txt
中的三个命令:
cmake_minmum_required()
用来指定所需的CMake
的最低版本。project()
中的参数表示项目名称。add_excutable()
中有两个参数Demo
和main.cc
,意思是将main.cc
源文件编译成一个名为Demo
的可执行文件。在编写完CMakeList.txt
以后,执行cmake ${CMAKELIST_PATH}
:
shi@shi-Z370M-S01:~/Demo_1$ cmake . -- Configuring done -- Generating done -- Build files have been written to: /home/shi/Demo_1
会在按照文件中的配置和参数,在当前目录生成编译所须要的一系列文件,其中包含Makefile
文件。
cmake
命令是能够附带各类参数的,在命令后用空格隔开使用,--build
,--target
等。~/Demo_1 | +--CMakeFiles +--CMakeCache.txt +--cmake_install.cmake +--CMakeList.txt +--main.cpp +--Makefile
生成Makefile
后再执行make ${MAKEFILE_PATH}
命令便可编译获得Demo
可执行文件。
shi@shi-Z370M-S01:~/Demo_1$ make Scanning dependencies of target Demo [ 50%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o [100%] Linking CXX executable Demo [100%] Built target Demo
运行编译后的可执行文件:
shi@shi-Z370M-S01:~/Demo_1$ ./Demo 2 10 2^10 is 1024
在实际编程过程当中,若是有多个源文件,好比将Demo_1.cpp
中的power函数单独写进一个mathfunctions.cpp
源文件中:
/* * power - Calculate the power of number. * @param base: Base value. * @param exponent: Exponent value. * * @return base raised to the power exponent. */ double power(double base, int exponent) { int result = base; int i; if (exponent == 0) { return 1; } for(i = 1; i < exponent; ++i){ result = result * base; } return result; }
而后在main函数中引用MathFunctions.cpp
的power
函数来进行计算:
#ifndef POWER_H #define POWER_H extern double power(double base, int exponent); #endif
#include <iostream> // import power function #include "MathFunctions.h" int main(int argc, char *argv[]) { if (argc < 3){ // printf("Usage: %s base exponent \n", argv[0]); cout << "Usage: " << argv[0] << "base exponent" <<endl; return 1; } double base = atof(argv[1]); int exponent = atoi(argv[2]); double result = power(base, exponent); //printf("%g ^ %d is %g\n", base, exponent, result); cout << base << "^" << exponent << " is " << result <<endl; return 0; }
令工程变成以下结构:
~/Demo_2 | +--CMakeList.txt +--main.cpp +--MathFunctions.cpp +--MathFunctions.h
这时咱们只须要在CMakeList.txt
中的add_executable()
命令上添加新参数,变成这样就能够了:
# CMake 最低版本号要求 cmake_minimum_required (VERSION 2.8) # 项目信息 project (Demo_1) # 指定生成目标 add_executable(Demo "main.cpp" "MathFunctions.cpp")
这里咱们经过CMake
告诉了计算机,咱们要使用主文件main.cpp
和函数文件MathFunctions.cpp
来生成可执行程序Demo
。
可是在源文件数量较多的时候,在add_executable()
命令上添加新参数就会变得很麻烦,这时候咱们可使用aux_source_directory()
命令:
aux_source_directory(<dir> <variable>)
这个命令会查找<dir>
参数的目录下全部的源文件,而后将结果存进<variable>
变量中,所以能够修改CMakeList.txt
以下:
# CMake 最低版本号要求 cmake_minimum_required (VERSION 2.8) # 项目信息 project (Demo_1) # 查找当前目录下的全部源文件 # 并将名称保存到 DIR_SRCS 变量 aux_source_directory(. DIR_SRCS) # 指定生成目标 add_executable(Demo ${DIR_SRCS})
编译运行一下:
shi@shi-Z370M-S01:~/Demo_2$ cmake . -- The C compiler identification is GNU 7.4.0 -- The CXX compiler identification is GNU 7.4.0 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/shi/Demo_2 shi@shi-Z370M-S01:~/Demo_2$ make Scanning dependencies of target Demo [ 33%] Building CXX object CMakeFiles/Demo.dir/MathFunctions.cpp.o [ 66%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o [100%] Linking CXX executable Demo [100%] Built target Demo shi@shi-Z370M-S01:~/Demo_2$ ./Demo 2 10 2^10 is 1024
接下来咱们将MathFunctions.cpp
和MathFunctions.h
文件移动到math目录下:
~/Demo_3 | +--CMakeList.txt +--main.cpp +--math/ | +--CMakeList.txt +--MathFunctions.cpp +--MathFunctions.h
这时候项目工程分红了主程序和库文件两个部分,这时候须要咱们分别在根目录~/Demo_3
和库目录~/Demo_3/math/
各编写一个CMakeList.txt
文件,将库目录~/Demo_3/math/
里的文件编译成静态库再由根目录主程序main.cpp
中的main()
函数调用。
库目录~/Demo_3/math/
下的CMakeList.txt
:
# 查找当前目录下的全部源文件 # 并将名称保存到 DIR_LIB_SRCS变量 aux_source_directory(. DIR_LIB_SRCS) add_library(MathFunctions ${DIR_LIB_SRCS})
以前咱们使用add_executable()
命令,编译生成可执行文件,在这个库目录的CMakeList.txt
文件中,咱们使用了add_library()
函数,来将库目录中的文件编译为静态链接库。
这样咱们先告诉了计算机,咱们须要用库文件math
目录下的源文件,组成一个名为MathFunctions
的库(library)。
以后咱们回到根目录~/Demo_3/
中,修改根目录的CMakeList.txt
文件以下:
# CMake 最低版本号要求 cmake_minimum_required(VERSION 2.8) # 项目信息 project(Demo_3) # 查找当前目录下的全部源文件 # 并将名称保存到 DIR_SRCS 变量 # aux_source_directory(. DIR_SRCS) # 添加 math 子目录 add_subdirectory(math) # 指定生成目标 add_executable(Demo main.cpp) # 添加连接库 target_link_libraries(Demo MathFunctions)
在根目录新的CMakeList.txt
中,咱们使用add_subdirectory()
命令,将库目录/math
添加为一个辅助目录(subdirectory),告诉计算机咱们将要用到这个目录下的文件或者库。
以后咱们使用target_link_libraries()
命令,将库目录/math
下,已经在上一个CMakeList.txt
中定义好的库MathFunctions
与咱们想要生成的可执行文件Demo
相链接。
main.cpp
的头文件引用:// #include "MathFunctions.h" old #include "math/MathFunctions.h" // new
编译并运行一下:
shi@shi-Z370M-S01:~/Demo_3$ cmake . -- The C compiler identification is GNU 7.4.0 -- The CXX compiler identification is GNU 7.4.0 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/shi/Demo_3 shi@shi-Z370M-S01:~/Demo_3$ make [ 50%] Built target MathFunctions Scanning dependencies of target Demo [ 75%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o [100%] Linking CXX executable Demo [100%] Built target Demo shi@shi-Z370M-S01:~/Demo_3$ ./Demo 2 10 2^10 is 1024
CMake
能够自定义编译选项,从而能够容许咱们根据用户的环境和需求,选择最合适的编译方案,好比能够将上一节的咱们本身写的MathFunctions
库设置为一个可选的库,从而能够自由选择编译时是使用咱们本身建立的库,仍是调用标准库中的数学函数。
为此咱们须要在顶层的CMakeLists.txt
中添加选项。
首先须要在其中添加:
# 加入一个配置头文件,用于处理 CMake 对源码的设置 configure_file ( "${PROJECT_SOURCE_DIR}/config.h.in" "${PROJECT_BINARY_DIR}/config.h" )
configure_file()
命令用来加入一个配置头文件config.h
,这个文件将由CMake
从config.h.in
生成,经过这个机制,咱们能够预约义一些参数和变量来控制代码生成。
而后再添加:
# 是否使用本身的 MathFunctions 库 option (USE_MYMATH "Use provided math implementation" ON) # 是否加入 MathFunctions 库 if (USE_MYMATH) include_directories ("${PROJECT_SOURCE_DIR}/math") add_subdirectory (math) set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions) endif (USE_MYMATH)
option()
命令中,咱们定义了一个名为USE_MYMATH
变量,并设置其默认值为ON
。
随后的if()
根据USE_MYMATH
变量的值来决定咱们是否使用本身编写的MathFunctions
库。若是是,则添加头文件目录${PROJECT_SOURCE_DIR}/math
,子目录math
,并将可选库地址存于EXTRA_LIBS
。
完整的CMakeList.txt
以下:
# CMake最低版本号要求 cmake_minimum_required(VERSION 2.8) # 项目信息 project(Demo_4) # 加入一个配置头文件,用于处理CMake对源码的设置 configure_file( "${PROJECT_SOURCE_DIR}/config.h.in" "${PROJECT_BINARY_DIR}/config.h" ) # 是否使用本身的MathFunctions库 option(USE_MYMATH "Use provided math implementation" ON ) # 是否加入MathFunctions库 if (USE_MYMATH) # 添加头文件路径 include_directories("${PROJECT_SOURCE_DIR}/math") # 添加math子目录 add_subdirectory(math) # 收集可选库地址存于EXTRA_LIBS set(EXTRA_LIBS ${EXTRA_LIBS} MathFunctions) endif (USE_MYMATH) # 查找当前目录下的全部源文件,并将名称保存到 DIR_SRCS 变量 aux_source_directory(. DIR_SRCS) # 指定生成目标 add_executable(Demo ${DIR_SRCS}) # 添加连接库 target_link_libraries(Demo ${EXTRA_LIBS})
这时咱们须要生成的可执行文件Demo
是由DIR_SRCS
目录中的源文件生成,并与EXTRA_LIBS
目录下的库进行链接。
配置文件config.h
并不须要咱们直接编写,咱们须要编写一个config.h.in
文件,来让CMake
自动生成config,h
,内容以下:
#cmakedefine USE_MYMATH
工程目录结构以下:
~/Demo_4 | +--CMakeList.txt +--main.cpp +--config.h.in +--math/ | +--CMakeList.txt +--MathFunctions.cpp +--MathFunctions.h
以后咱们须要修改咱们的源文件main.cpp
,咱们须要引用新的配置头文件config.h
,让其根据USE_MYMATH
的预约义值来选择调用MathFunctions
库仍是标准库。
修改后的main.cpp
文件:
#include <iostream> // #include "math/MathFunctions.h" #include "config.h" //判断函数的调用 #ifdef USE_MYMATH #include "math/MathFunctions.h" #else #include<math.h> #endif using namespace std; int main(int argc, char *argv[]) { if (argc < 3){ cout << "Usage: " << argv[0] << "base exponent" <<endl; return 1; } double base = atof(argv[1]); int exponent = atoi(argv[2]); #ifdef USE_MYMATH // 调用自制库 cout << "Using our own math lib. "<<endl; double result = power(base, exponent); #else // 调用标准库 cout << "Using standard math lib. "<<endl; double result = pow(base, exponent); #endif cout << base << "^" << exponent << " is " << result <<endl; return 0; }
编译一下:
shi@shi-Z370M-S01:~/Demo_4$ cmake . -- The C compiler identification is GNU 7.4.0 -- The CXX compiler identification is GNU 7.4.0 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/shi/Demo_4 shi@shi-Z370M-S01:~/Demo_4$ make Scanning dependencies of target MathFunctions [ 25%] Building CXX object math/CMakeFiles/MathFunctions.dir/MathFunctions.cpp.o [ 50%] Linking CXX static library libMathFunctions.a [ 50%] Built target MathFunctions Scanning dependencies of target Demo [ 75%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o [100%] Linking CXX executable Demo [100%] Built target Demo shi@shi-Z370M-S01:~/Demo_4$ ./Demo 2 10 Using standard math lib. 2^10 is 1024
这里出现了一个问题,用默认的cmake
命令编译以后,执行可执行文件Demo
时发现,并无引用咱们本身编写的库,而是引用了标准库。
以后检查全部文件没有发现问题,安装带gui的CMake
后,执行ccmake
命令,在gui中显示USE_MYMATH
正常的被配置为ON
,执行编译后运行:
shi@shi-Z370M-S01:~/Demo_4$ ./Demo 2 10 Using our own math lib. 2^10 is 1024
此次则正常调用了本身编写的库,目前缘由未知。(2019.10.21)