CMake是一个类似于QMake的构建工具,你可以理解成它是用来生成makefile的工具。具体的介绍请看官方文档 。
测试环境:Windows 10, Microsoft Visual Studio 2019
所有测试代码在这里 。
Step 1: Hello CMake 我们从一个最简单的例子入手。
1 2 3 4 5 6 7 8 cmake_minimum_required (VERSION 3.10 )set (CMAKE_CXX_STANDARD 17 )set (CMAKE_CXX_STANDARD_REQUIRED True )add_executable (Tutorial tutorial.cxx)
tutorial.cxx:
1 2 3 4 5 6 7 #include  <iostream>  #include  <filesystem>  int  main (int  argc, char * argv[])    std ::cout  << "Hello CMake!"  << std ::endl ;   std ::cout  << std ::filesystem::temp_directory_path().u8string() << std ::endl ;   return  0 ; } 
这个例子中,我们设定了C++标准。
构建和运行 首先,用cmake生成MSVC的相应工程文件
1 2 3 4 $ cd  step1 $ mkdir build $ cd  build $ cmake .. 
再编译、链接,生成最终的可执行文件
运行一下:
1 2 3 $ ./Tutorial.exe Hello CMake! C:\Users\ADMINI~1\AppData\Local\Temp\ 
默认编译的是Debug版本,若需编译Release版本,需指定config
1 $ cmake --build . --config Release 
若需要指定编译器,指定平台,可以这样
1 2 3 4 5 $ cd  step1 $ cmake -G "Visual Studio 16 2019"  -A Win32 -S \path_to_source\ -B "build32"  $ cmake -G "Visual Studio 16 2019"  -A x64 -S \path_to_source\ -B "build64"  $ cmake --build build32 --config Release $ cmake --build build64 --config Release 
给工程添加版本号和对应的头文件 添加版本号
1 project (Tutorial VERSION 1.0 )
然后,配置一个头文件
1 configure_file (TutorialConfig.h.in TutorialConfig.h)
TutorialConfig.h.in
1 2 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@ #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@ 
构建时,将会生成在可执行文件所在目录生成一个TutorialConfig.h。我们需要在项目中使用它,需要添加头文件包含目录。
1 target_include_directories (Tutorial PUBLIC "${PROJECT_BINARY_DIR}" )
修改tutorial.cxx测试一下
1 2 3 4 5 6 7 8 9 #include  <iostream>  #include  <filesystem>  #include  "TutorialConfig.h"  int  main (int  argc, char * argv[])    std ::cout  << "Hello CMake!"  << std ::endl ;   std ::cout  << argv[0 ] << " Version "  << Tutorial_VERSION_MAJOR << "."  << Tutorial_VERSION_MINOR << std ::endl ;   std ::cout  << std ::filesystem::temp_directory_path().u8string() << std ::endl ;   return  0 ; } 
Step 2: 添加一个库 我们来添加一个实现开方的数学函数库,再在主工程中调用它。
它的目录结构像这样:
1 2 3 4 5 6 7 8 |--step2/ |  |--build/ |  |--MathFunctions/ |  |  |--mysqrt.h |  |  |--mysqrt.cxx |  |  |--CMakeLists.txt |  |--tutorial.cxx |  |--CMakeLists.txt 
实现mysqrtlib mysqrt.h
1 double  mysqrt (double  y) 
mysqrt.cxx
1 2 3 4 5 6 7 8 9 10 11 double  mysqrt (double  y)    double  x, z, tempf;   unsigned  long  *tfptr = ((unsigned  long  *)&tempf) + 1 ;   tempf = y;   *tfptr = (0xbfcdd90a  - *tfptr)>>1 ;    x =  tempf;   z =  y*0.5 ;                          x = (1.5 *x) - (x*x)*(x*z);   x = (1.5 *x) - (x*x)*(x*z);   return  x*y;  } 
再添加MathFunctions的CMakeLists.txt, 它只有一行
1 add_library (MathFunctions mysqrt.cxx)
在主工程中添加依赖 1 2 3 4 add_subdirectory (MathFunctions)add_executable (Tutorial tutorial.cxx)target_link_libraries (Tutorial PUBLIC MathFunctions)target_include_directories (Tutorial PUBLIC "${PROJECT_SOURCE_DIR}/MathFunctions" )
用add_subdirectory添加子目录,类似于qmake中的subdirs,以便于执行cmake时,会编译mysqrtlib。
用target_link_libraries添加mysqrtlib依赖,类似于qmake中的LIBS += -lmysqrtlib。
用target_include_directories添加头文件包含目录,类似于qmake中的 INCLUDEPATH += $$PWD/mysqrtlib。
然后在tutorial.cxx中调用mysqrt
1 2 3 4 5 6 7 #include  <iostream>  #include  "mysqrt.h"  int  main (int  argc, char * argv[])    std ::cout  << mysqrt(2 ) << std ::endl ;   return  0 ; } 
按照step1相同的步骤进行构建工程的生成和编译运行。
1 2 3 4 $ mkdir build $ cd  build $ cmake .. $ cmake --build . 
使用宏开关控制使用mysqrt还是sqrt 在实际的项目中,我们可以使用一些宏开关来进行条件编译。来操练一下。
修改tutorial.cxx的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include  <iostream>  #include  "TutorialConfig.h"  #ifdef  USE_MYMATH #include  "mysqrt.h"  #else  #include  <cmath>  #endif  int  main (int  argc, char * argv[])  #ifdef  USE_MYMATH   std ::cout  << "use mysqrt"  << std ::endl ;   std ::cout  << mysqrt(2 ) << std ::endl ; #else    std ::cout  << "use sqrt"  << std ::endl ;   std ::cout  << sqrt (2 ) << std ::endl ; #endif    return  0 ; } 
修改CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 option (USE_MYMATH "Use tutorial provided math implementation"  ON )configure_file (TutorialConfig.h.in TutorialConfig.h)if (USE_MYMATH)  add_subdirectory (MathFunctions)   list (APPEND EXTRA_LIBS MathFunctions)   list (APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions" ) endif ()add_executable (Tutorial tutorial.cxx)target_link_libraries (Tutorial PUBLIC ${EXTRA_LIBS} )target_include_directories (Tutorial PUBLIC "${PROJECT_BINARY_DIR}"  ${EXTRA_INCLUDES} )
在CMake的开头位置增加了一个Option,默认开启。
再在TutorialConfig.h.in中增加一个宏定义
然后添加了if 子句用来添加头文件包含文件和依赖库。
使用mysqrt 
1 2 3 $ cd  step2/build $ cmake .. $ cmake --build . 
不使用mysqrt 
1 2 3 $ cd  step2/build $ cmake .. -DUSE_MYMATH=OFF $ cmake --build . 
Step 3: 简化Step 2的配置 我们可以使用INTERFACE来让Step 2的CMake配置更简洁。
在mysqrtlib的CMake配置中,我们添加了target_include_directories, 但作用域描述为INTERFACE,而非PUBLIC。它是指消费者需要它,而生产者并不需要。
step3\MathFunctions\CMakeLists.txt
1 2 add_library (MathFunctions mysqrt.cxx)target_include_directories (MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} )
接下来,我们依赖mysqrtlib库时,就不需要处理头文件包含目录的逻辑了。
step3\CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 option (USE_MYMATH  "Use tutorial provided math implementation"  ON )configure_file (TutorialConfig.h.in TutorialConfig.h)if (USE_MYSQRT)  add_subdirectory (MathFunctions)   list (APPEND EXTRA_LIBS MathFunctions) endif ()add_executable (Tutorial tutorial.cxx)target_link_libraries (Tutorial PUBLIC ${EXTRA_LIBS} )target_include_directories (Tutorial PUBLIC "${PROJECT_BINARY_DIR}" )
构建,运行:
1 2 3 $ cd  step3/build $ cmake .. $ cmake --build . 
Step 4: 安装 当我们需要将头文件、库文件或可执行文件安装到某目录时,会用到install
例如上面的mysqrtlib,需要在mysqrtlib/CMakeLists.txt末尾这样添加:
1 2 install (TARGETS MathFunctions DESTINATION lib)install (FILES mysqrt.h DESTINATION include )
在主工程的CMakeLists.txt的末尾这样添加:
1 2 install (TARGETS Tutorial DESTINATION bin)install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"  DESTINATION include )
然后,在build目录下执行install命令
1 2 $ cd  step4/build $ cmake --install . 
如果我们要修改默认安装位置,需要这样
1 $ cmake --install . --prefix ../dist 
Step 7: 打包 通过在主工程的CMakeLists.txt末尾添加几行命令,我们可以使用CMake来打包。
1 2 3 4 5 6 include (InstallRequiredSystemLibraries)set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt" )set (CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}" )set (CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}" )set (CPACK_SOURCE_GENERATOR "ZIP" )include (CPack)
编译,打包
1 2 3 4 $ cd  step7/build $ cmake .. $ cmake --build . --config Release $ cpack 
指定打包器
补充 如何理解target_link_libraries中的PUBLIC、PRIVATE、INTERFACE 
如果你实现的库中的.cpp实现中需要用到QtNetwork,但不包含在头文件中提供给用户。那么QtNetwork是一个PRIVATE 依赖。 
如果你实现的库中的.cpp实现中用到了QtWidgets,且需要包含在头文件中提供给用户。那么QtWidgets是一个PUBLIC 依赖。 
如果你实现的库中的.cpp实现中不依赖XX,仅在头文件中作为接口提供给用户。那么XX是一个INTERFACE 依赖。 
 
Reference