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