CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。只是 CMake的组态档取名为CMakeLists.txt。Cmake并不直接建构出最终的软件,而是产生标准的建构档(如 Unix的Makefile或Windows Visual C++的projects/workspaces),然后再依一般的建构方式使用。这使得熟悉某个集成开发环境(IDE)的开发者可以用标准的方式建构他的软件,这种可以使用各平台的原生建构系统的能力是CMake和SCons等其他类似系统的区别之处。
至于出现一份代码,到处编译,但是编译不过就有可能是代码的问题了。
代码仓库
https://github.com/gongluck/CMake-Demo.git
简单项目
创建目录mydemo,再在mydemo目录中创建CMakeLists.txt和main.c两个文件。
//main.c
#include <stdio.h>
int main()
{
printf("hello, cmake!\n");
return 0;
}
#CMakelists.txt
# CMake最低版本要求
cmake_minimum_required(VERSION 3.0)
# 项目名称
project(mydemo)
# 查找当前目录下所有源文件并保存到变量
aux_source_directory(. SRCS)
# 指定生成目标
add_executable(mydemo ${SRCS})
之后执行命令:
cmake -S . -B ./build
cmake --build ./build
第一条命令是生成目标平台的项目,将生成使用系统上默认的编译套件的工程。-S后面的参数是CMake工程CMakeLists.txt文件所在目录,-B后面的参数是将要生成的目标平台项目文件存放的目录。 第二条命令是构建项目,当然也可以通过其他工具打开项目构建或者执行make命令等。–build是CMake程序构建的命令,后面的参数是需要构建的项目的路径。
增加子模块
创建目录myfun,再在myfun目录中创建CMakeLists.txt、myfun.h和myfun.c三个文件。
//myfun.h
#ifndef __MYFUN_H__
#define __MYFUN_H__
int myfun(int a, int b);
#endif//__MYFUN_H__
//myfun.c
#include "myfun.h"
int myfun(int a, int b)
{
return a+b;
}
#CMakelists.txt
# CMake最低版本要求
cmake_minimum_required(VERSION 3.0)
# 获取当前文件夹名
STRING(REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR})
# 项目名称
project(${CURRENT_FOLDER})
# 查找当前目录下所有源文件并保存到变量
aux_source_directory(. SRCS)
# 生成链接库
add_library(${PROJECT_NAME} ${SRCS})
子模块中的
# 生成链接库
add_library(${PROJECT_NAME} SHARED ${SRCS})
SHARED可以省略,省略后相当于STATIC。
mydemo下的CMakeLists.txt和main.c也要做修改。然后,重新构建生成。
//main.c
#include <stdio.h>
#include "myfun/myfun.h"
int main()
{
printf("hello, cmake!\n");
printf("1+1=%d\n", myfun(1, 1));
return 0;
}
#CMakelists.txt
# CMake最低版本要求
cmake_minimum_required(VERSION 3.0)
# 获取当前文件夹名
STRING(REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR})
# 项目名称
project(${CURRENT_FOLDER})
# 设置变量
set(SUBMODULE myfun)
# 添加子目录
# 必须放在aux_source_directory前,否则同名变量SRCS会冲突
add_subdirectory(${SUBMODULE})
# 查找当前目录下所有源文件并保存到变量
aux_source_directory(. SRCS)
# 指定生成目标
add_executable(${PROJECT_NAME} ${SRCS})
# 添加链接库
target_link_libraries(${PROJECT_NAME} ${SUBMODULE})
主模块中主要改动在
# 添加子目录
# 必须放在aux_source_directory前,否则同名变量SRCS会冲突
add_subdirectory(${SUBMODULE})
# 添加链接库
target_link_libraries(${PROJECT_NAME} ${SUBMODULE})
add_subdirectory命令添加子模块目录,CMake会自动执行子模块中的CMakelists.txt。 target_link_libraries命令是将子模块链接到主模块。
自定义编译选项
主模块的CMakelists.txt修改成:
#CMakeLists.txt
# CMake最低版本要求
cmake_minimum_required(VERSION 3.0)
# 获取当前文件夹名
STRING(REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR})
# 项目名称
project(${CURRENT_FOLDER})
# 加入一个配置头文件用于处理 CMake 对源码的设置
configure_file(
${PROJECT_SOURCE_DIR}/config.h.in
${PROJECT_BINARY_DIR}/config.h
)
# 自定义编译选项
option(USESUBMODULE "use submodule" ON)
if (USESUBMODULE)
# 设置变量
set(SUBMODULE myfun)
# 添加包含路径
include_directories(${SUBMODULE})
# 添加子目录
# 必须放在aux_source_directory前,否则同名变量SRCS会冲突
add_subdirectory(${SUBMODULE})
# 设置附加库变量
set(EXTRA_LIBS ${EXTRA_LIBS} ${SUBMODULE})
endif (USESUBMODULE)
# 查找当前目录下所有源文件并保存到变量
aux_source_directory(. SRCS)
# 指定生成目标
add_executable(${PROJECT_NAME} ${SRCS})
# 添加链接库
target_link_libraries(${PROJECT_NAME} ${EXTRA_LIBS})
创建config.h.in文件并输入:
#cmakedefine USEMYPRINT
option命令可以设置自定义编译选项,configure_file命令用于设置配置文件。生成目标工程时,目标目录会生成一个config.h文件。config.h中,如果USESUBMODULE选项被打开就为:
#define USESUBMODULE
否则是:
/* #undef USESUBMODULE */
main.c也需要调整:
//main.c
#include <stdio.h>
#ifdef USESUBMODULE
#include "myfun.h"
#endif//USESUBMODULE
int main()
{
printf("hello, cmake!\n");
#ifdef USESUBMODULE
printf("1+1=%d\n", myfun(1, 1));
#endif//USESUBMODULE
return 0;
}
CMake打开选项的命令:
cmake -D[宏名]=[宏值]
所以,开关USESUBMODULE选项使用:
cmake -S . -B ./build -DUSESUBMODULE=ON
cmake -S . -B ./build -DUSESUBMODULE=OFF
安装
CMakeLists.txt的install命令:
# 安装目标文件
install(TARGETS [名称] DESTINATION [目录])
# 安装普通文件
install(FILES [名称] DESTINATION [目录])
主模块和子模块的CMakeLists.txt分别添加对应的install命令:
# 指定安装路径
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/config.h" DESTINATION include)
# 指定库的安装路径
install(TARGETS ${PROJECT_NAME} DESTINATION lib)
install(FILES ${PROJECT_NAME}.h DESTINATION include)
最后执行:
cmake -S ./ -B ./build
cmake --build ./build --config Release
cmake --install ./build --config Release --prefix ./install
cmake –install ./build命令就是执行 ./build目录中项目的安装,–prefix ./install参数表示安装到 ./install目录。
测试
启用测试:
# 启用测试
enable_testing()
添加测试使用:
add_test(NAME [测试名] COMMAND [测试执行命令及参数] WORKING_DIRECTORY [工作目录])
set_tests_properties([测试名] PROPERTIES PASS_REGULAR_EXPRESSION [匹配输出])
主模块CMakeLists.txt添加:
# 分别设置了Debug版本和Release版本可执行文件的输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin)
# 分别设置了Debug版本和Release版本库文件的输出目录
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/lib)
# 定义一个宏,用来简化测试工作
macro(do_test mycommand myret)
add_test(NAME test_${mycommand}_${myret} COMMAND ${mycommand} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# 检查测试输出是否包含"${myret}"
set_tests_properties(test_${mycommand}_${myret} PROPERTIES PASS_REGULAR_EXPRESSION ${myret})
endmacro(do_test)
# 启用测试
enable_testing()
# 测试程序
do_test(mydemo "cmake")
执行命令编译和测试:
cmake -S ./ -B ./build
cmake --build ./build --config Release
cd ./build
ctest --force-new-ctest-process -C Release
函数检查
主模块CMakeLists.txt添加:
# 添加函数检查功能
include(CheckFunctionExists)
check_function_exists(printf HAVEPRINTF)
if(HAVEPRINTF)
# 添加宏定义
add_definitions(-DHAVEPRINTF)
endif()
检查printf函数并且定义HAVEPRINTF宏。 main.c也需要调整:
#include "config.h"
#include <stdio.h>
#ifdef USESUBMODULE
#include "myfun.h"
#endif//USESUBMODULE
int main()
{
printf("hello, cmake!\n");
#ifdef USESUBMODULE
printf("1+1=%d\n", myfun(1, 1));
#endif//USESUBMODULE
#ifdef HAVEPRINTF
puts("found printf.");
#else
puts("not found printf.");
#endif//HAVEPRINTF
return 0;
}
执行命令生成:
cmake -S ./ -B ./build
很不幸,生成Visual Studio工程时,printf函数总是找不到,但puts这类却是可以找到
添加版本号
主模块CMakeLists.txt添加:
# 添加版本号
set(VERSION_MAJOR 1)
set(VERSION_MINOR 0)
config.h.in添加:
#define VERSION_MAJOR @VERSION_MAJOR@
#define VERSION_MINOR @VERSION_MINOR@
这样生成的config.h文件中就会增加:
#define VERSION_MAJOR 1
#define VERSION_MINOR 0
在main.c中就可以直接使用版本号:
printf("hello, cmake!version:%d.%d\n", VERSION_MAJOR, VERSION_MINOR);
制作安装包
主模块CMakeLists.txt添加:
# 构建一个CPack安装包
include(InstallRequiredSystemLibraries)
# 设置安装包版本号
set(CPACK_PACKAGE_VERSION_MAJOR "${VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${VERSION_MINOR}")
include(CPack)
依次执行以下命令即可生成安装包:
cmake -S . -B ./build
cmake --build ./build --config Release
cpack --config ./build/CPackConfig.cmake -B E:\Code\CMAKE-DEMO\mydemo\package -C Release
cpack --config ./build/CPackSourceConfig.cmake -B E:\Code\CMAKE-DEMO\mydemo\package -C Release
cpack命令的参数 -B后面是的安装包的生成目录,需要绝对路径。
最后
CMake、CTest和CPack的基本使用基本就这些了,生成工程、编译、测试、安装、制作包都基本使用到了。每个命令还有很多可选的功能没有提到,等以后需要再做补充。
Last modified on 2020-05-17