好奇的探索者,理性的思考者,踏实的行动者。
Table of Contents:
cmake允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。
内部构建和外部构建
cmake强烈推荐的是外部构建(out-of-source build),好处是不影响源码。即新建build目录,构建的工作和生成的中间文件都在这个目录
查看更详细的编译信息
make VERBOSE=1 来构建
cmake进行编译并打印详细信息
cmake.EXE --build d:/project/c++/shipar/build --config Debug --target ALL_BUILD -j 22 --verbose
参考资料:
知乎问题 CMake 如何入门?:https://www.zhihu.com/question/58949190
关于cmake由浅入深的例子
https://github.com/ttroy50/cmake-examples
awesome-cmake
https://github.com/onqtam/awesome-cmake
Learn CMake's Scripting Language in 15 Minutes
https://preshing.com/20170522/learn-cmakes-scripting-language-in-15-minutes/
有时候改了cmake中的选项,比如:
option(CCACHE "Use ccache if available" OFF)
由ON改成OFF,但是打印CCACHE的值还是不变。这些选项被缓存在CMakeCache.txt中了。
对于这种情况可以直接删掉build目录,或者编译时加上选项,比如
cmake .. -DCCACHE=OFF
默认情况下.cpp文件的编译方式为g++,以c结尾的文件编译方式为gcc。
当存在c文件和c++文件混编的情况下要注意,若c文件用gcc编译,cpp文件用g++编译,可能会有问题。
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
# 添加可执行文件
add_executable(MyApp main.cpp apf.cpp)
编译发生的目录,即build目录
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR
工程顶层目录
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
<projectname>_SOURCE_DIR
当前处理的CMakeLists.txt 所在的路径,即用sub-projects and directories.
CMAKE_CURRENT_SOURCE_DIR
CMAKE_CURRENT_BINARY_DIR The build directory you are currently in.
CMake 中有一个变量 CMAKE_BUILD_TYPE ,可以的取值是 Debug Release RelWithDebInfo 和 MinSizeRel。当这个变量值为 Debug 的时候,CMake 会使用变量 CMAKE_CXX_FLAGS_DEBUG 和 CMAKE_C_FLAGS_DEBUG 中的字符串作为编译选项生成 Makefile ,当这个变量值为 Release 的时候,工程会使用变量 CMAKE_CXX_FLAGS_RELEASE 和 CMAKE_C_FLAGS_RELEASE 选项生成 Makefile。
cmake_minimum_required(VERSION 2.8.9)
project(hello)
set(CMAKE_CXX_STANDARD 17)
# 编译工具设置
# 设置链接器为ld.gold, 路径一般在/usr/bin/ld.gold下面
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold")
# 编译类型相关 Debug or Release
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -rdynamic")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -rdynamic")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -rdynamic")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -rdynamic")
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()
message("CMAKE_BUILD_TYPE is:${CMAKE_BUILD_TYPE}")
# message("CMAKE_CXX_FLAGS_DEBUG :================")
# message(${CMAKE_CXX_FLAGS_DEBUG})
# message("CMAKE_CXX_FLAGS_RELEASE :================")
# message(${CMAKE_CXX_FLAGS_RELEASE})
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT CMAKE_BUILD_TYPE STREQUAL "Release")
message(FATAL_ERROR "CMAKE_BUILD_TYPE must be Debug or Release!!!")
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Release")
endif()
# 设置编译输出位置
# 应该把这两条指令写在工程的 CMakeLists.txt 还是 src 目录下的CMakeLists.txt?
# 写在ADD_EXECUTABLE 或 ADD_LIBRARY命令的文件中
set(EXECUTABLE_OUTPUT_PATH ${ROOT_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
# 执行某个脚本
execute_process(COMMAND sh ${CMAKE_SOURCE_DIR}/make_depend.sh)
# 1.动态库
add_library(hello_library STATIC
${SOURCES}
)
# 2.静态库
add_library(hello_library SHARED
${SOURCES}
)
# 3.可执行文件
add_executable(hello ${SOURCES})
# 4.自定义目标
add_custom_target()
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
cmake在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,
所以要用CLEAN_DIRECT_OUTPUT来设置一下不要删除相同的目标
CLEAN_DIRECT_OUTPUT:Do not delete other variants of this target
在一个项目中不可避免会有一些测试代码,这些测试代码,我们并不一定需要每次都编译,尤其是编译正式版本的时候,这些测试代码是不会加入release版本的。为了加快编译速度,可以将这些测试用的target或不会加入release的target 加上EXCLUDE_FROM_ALL属性就不需要每次编译它了。
# fctest指定了EXCLUDE_FROM_ALL 属性,不会自动编译,只能手动编译
add_executable(fctest EXCLUDE_FROM_ALL FeatureCompareSpeedTest.cpp)
targets
A CMake script defines targets using the add_executable, add_library or add_custom_target commands properties
Once a target is created, it has properties that you can manipulate using the get_property()
and set_property()
Unlike variables, targets are visible in every scope, even if they were defined in a subdirectory.
All target properties are strings.
# Get the target's SOURCES property and assign it to MYAPP_SOURCES
get_property(MYAPP_SOURCES TARGET MyApp PROPERTY SOURCES)
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
# 获取属性的名字
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
# 设置动态库版本号 VERSION 指代动态库版本,SOVERSION 指代 API 版本。
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
# 添加宏定义
target_compile_definitions(libWAVM PUBLIC "WAVM_API=__attribute__((visibility(\"default\")))")
# 文件列表
set(SOURCES
src/Hello.cpp
src/main.cpp
)
# 模式匹配
file(GLOB SOURCES "src/*.cpp")
# should we use our own math functions?
option (USE_MYMATH
"Use tutorial provided math implementation" ON)
# add the MathFunctions library?
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial ${EXTRA_LIBS})
# 这将使用USE_MYMATH的设置来确定是否应该编译和使用mathfunction库。
# 注意,使用一个变量(在本例中是EXTRA_LIBS)来设置可选的库,然后将它们链接到可执行文件中。
# 这是一种常见的方法,用于保持较大的项目具有许多可选组件。
源码中使用
首先在Configure.h.in文件中添加以下内容:
#cmakedefine USE_MYMATH
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif
#ifdef USE_MYMATH
double outputValue = mysqrt(inputValue);
#else
double outputValue = sqrt(inputValue);
#endif
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
# Copy a file to another location and modify its contents.
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_BINARY_DIR}/TutorialConfig.h"
)
TutorialConfig.h.in
// @xx@ 中的变量会被替换掉
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
1.
include_directories(include)
2.
target_include_directories(${PROJECT_NAME}
PRIVATE
${PROJECT_SOURCE_DIR}/Include
)
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
1.
target_link_libraries(${PROJECT_NAME} "pthread" )
2.
add_library(glut32 STATIC IMPORTED)
set_property(TARGET glut32 PROPERTY IMPORTED_LOCATION ${ROOT_DIR}/Library/glut32.lib)
target_link_libraries(${PROJECT_NAME} glut32 ) #添加glut32.lib
安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。
变量CMAKE_INSTALL_PREFIX 用法:cmake -DCMAKE_INSTALL_PREFIX=/usr .
默认是在DESTINATION目录下
# DESTINATION 在这里是一个相对路径,取默认值。在unix系统中指向 /usr/local 在windows上c:/Program Files/${PROJECT_NAME}。
# 指定target安装目录, make install时生效
install(TARGETS testStudent DESTINATION /usr/lib)
# 自定义文件复制
file(COPY ${ROOT_DIR}/Library/glut32.dll
DESTINATION ${EXECUTABLE_OUTPUT_PATH}/Release)
# 目标文件
INSTALL(TARGETS targets... [ARCHIVE|LIBRARY|RUNTIME] [DESTINATION dirname] [PERMISSIONS permissions...] [...])
# 普通文件
INSTALL(FILES files... DESTINATION <dir> [PERMISSIONS permissions...])
# 目录的安装
INSTALL(DIRECTORY dirs... DESTINATION <dir> [FILE_PERMISSIONS permissions...])
安装时CMAKE脚本的执行:INSTALL([[SCRIPT <file>] [CODE <code>]] [...])
在CMake中提供了add_custom_command和add_custom_target用来为某个目标或库添加一些自定义命令,该命令本身会成为目标的一部分,仅在目标本身被构建时才会执行。如果该目标已经构建,命令将不会执行。
add_custom_command: 增加自定义的构建规则到生成的构建系统中
add_custom_command(TARGET target
PRE_BUILD | PRE_LINK| POST_BUILD
COMMAND command1[ARGS] [args1...]
[COMMAND command2[ARGS] [args2...] ...]
[WORKING_DIRECTORYdir]
[COMMENT comment][VERBATIM])
cmake -P hello.txt
# 命令行中设置
cmake -DNAME=Newman
# 脚本中设置
set(THING "funk")
# 基本语法规则:
# 1. 变量使用${}方式取值,但是在IF控制语句中是直接使用变量名
# 2. 指令(参数1 参数2...)
# 参数使用括弧括起,参数之间使用空格或分号分开
# 指令是大小写无关的,参数和变量是大小写相关的
# 参数加不加引号都可以
# SET(SRC_LIST main.c)也可以写成 SET(SRC_LIST "main.c")
You Can Simulate a Data Structure using Prefixes
变量可以嵌套使用
set(JOHN_NAME "John Smith")
set(JOHN_ADDRESS "123 Fake St")
set(PERSON "JOHN")
message("${${PERSON}_NAME} lives at ${${PERSON}_ADDRESS}.")
every statement is a command that takes a list of string arguments and has no return value
math(EXPR MY_SUM "1 + 1") # Evaluate 1 + 1; store result in MY_SUM
数字比较 LESS,GREATER,EQUAL
字符串比较 STRLESS、STRGREATER、STREQUAL
cmake所有commands https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html
Even flow control statements are commands.
# 缩进不是必须的
if(WIN32)
message("You're running CMake on Windows.")
endif()
# 循环
set(A "1")
set(B "1")
while(A LESS "1000000")
message("${A}") # Print A
math(EXPR T "${A} + ${B}") # Add the numeric values of A and B; store result in T
set(A "${B}") # Assign the value of B to A
set(B "${T}") # Assign the value of T to B
endwhile()
Lists are Just Semicolon-Delimited Strings
set(ARGS "EXPR;T;1 + 1") #此时的ARGS就是一个列表
math(${ARGS}) # Equivalent to calling math(EXPR T "1 + 1")
set(ARGS "EXPR;T;1 + 1") #加引号会原文输出,不加引号会被当做多个值
message("${ARGS}") # Prints: EXPR;T;1 + 1
# 列表在for循环中的应用
foreach(ARG These are separate arguments)
message("${ARG}") # Prints each word on a separate line
endforeach()
foreach(ARG ${MY_LIST}) # Splits the list; passes items as arguments
message("${ARG}") # Prints each item on a separate line
endforeach()
Functions Run In Their Own Scope; Macros Don’t
函数里的变量不会污染全局的变量,若要在函数中设置变量的值,需要传参并用set设值
函数可以访问函数外部的全局变量
# VALUE为参数
function(doubleIt VALUE)
math(EXPR RESULT "${VALUE} * 2")
message("${RESULT}")
endfunction()
doubleIt("4") # Prints: 8
# 改变外部变量
function(doubleIt VARNAME VALUE)
math(EXPR RESULT "${VALUE} * 2")
set(${VARNAME} "${RESULT}" PARENT_SCOPE) # Set the named variable in caller's scope
endfunction()
doubleIt(RESULT "4") # Tell the function to set the variable named RESULT
message("${RESULT}") # Prints: 8
# 上述函数用宏实现
macro(doubleIt VARNAME VALUE)
math(EXPR ${VARNAME} "${VALUE} * 2") # Set the named variable in caller's scope
endmacro()
doubleIt(RESULT "4") # Tell the macro to set the variable named RESULT
message("${RESULT}") # Prints: 8
# 无参函数会把list当做参数,并用ARGN来引用
function(doubleEach)
foreach(ARG ${ARGN}) # Iterate over each argument
math(EXPR N "${ARG} * 2") # Double ARG's numeric value; store result in N
message("${N}") # Print N
endforeach()
endfunction()
doubleEach(5 6 7 8) # Prints 10, 12, 14, 16 on separate lines
命令可以包含进来另一个脚本,include的搜索路径的变量在CMAKE_MODULE_PATH中
The find_package command looks for scripts of the form Find*.cmake and also runs them in the same scope.
Such scripts are often used to help find external libraries.
find_package()命令首先会在模块路径中寻找Find.cmake,这是查找库的一个典型方式。具体查找路径依次为CMake:变量${CMAKE_MODULE_PATH}中的所有目录。如果没有,然后再查看它自己的模块目录/share/cmake-x.y/Modules/
命令查找: find / -name Find*.cmake |less
find_package的主要作用是生成一些变量:如:xxx_INCLUDE_DIRS
,xxx_LIBRARIES
, 方便集成三方库
find_package(OpenSceneGraph REQUIRED osgDB osgUtil osgViewer osgGA osgText)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PRIVATE ${OPENSCENEGRAPH_LIBRARIES})
find_package
是 CMake 中一个用于查找并加载外部库或工具包的函数。它的工作原理涉及一系列步骤,用于在系统上查找所需的库或工具包,并设置相应的变量以便后续使用。
这里是 find_package
的主要工作步骤:
查找模块文件: find_package
需要一个 Find<PackageName>.cmake
文件或 <PackageName>Config.cmake
文件来帮助定位库或工具包。这些文件通常由库或工具包的提供者编写,并包含了指导 CMake 如何找到和使用该包的信息。
搜索位置: find_package
会在预定义的一系列目录中搜索模块文件。这些目录包括 CMake 的模块路径 (CMAKE_MODULE_PATH
),系统环境变量中的路径,以及其他用户定义的路径。
执行模块文件: 找到适当的模块文件后,find_package
会执行该文件。这个文件的内容会告诉 CMake 如何找到包,设置必要的变量以便在项目中使用。
设置变量: 如果找到了所需的包,find_package
会设置一系列变量,例如 <PackageName>_FOUND
表示是否找到该包,<PackageName>_INCLUDE_DIRS
表示包含文件的目录,<PackageName>_LIBRARIES
表示库文件的路径等等。这些变量允许你在 CMakeLists.txt 文件中使用该包。
报告结果: find_package
会将结果报告给 CMake 构建系统,这样你就可以根据找到的包是否可用来设置其他构建选项或路径。
需要注意的是,不同的包提供者可能会有不同的 Find<PackageName>.cmake
文件或 <PackageName>Config.cmake
文件。一些包可能会使用基于环境变量的方法,而另一些则可能提供了更复杂的配置文件,包含了库的位置、依赖项和编译选项等信息。
总的来说,find_package
提供了一个灵活且通用的方法来管理依赖项,但其准确的工作方式取决于每个特定包提供的支持文件和 CMake 的配置。
CMake’s add_subdirectory command, on the other hand, creates a new scope, then executes the script named CMakeLists.txt from the specified directory in that new scope
Windows上使用CMake也很方便,除了传统的命令行方式使用CMake,还有一个简单的GUI程序cmake-gui.exe来使用CMake。
运行cmake-gui
source code编辑框就是输入代码的所在的路径,这个路径能够找到一个CMakeLists.txt文件。
build the binaries编辑框就是编译输出的中间文件和最终的二进制文件的目录。
Add Entry添加预定义的变量
因为CMake最终通过CMakeLists.txt文件生成Windows上对应的vs工程文件,不同的vs版本也会影响到最终生成vs工程文件,所以configure对话框就是选择代码编译工具的,如图所示: