CMake与VisualStudio工程配置映射
suhao opened this issue · comments
本文将迁移至Rapid C++: CMake与VisualStudio工程配置映射
本文整理了工作中常用的CMake与VisualStudio工程配置的映射关系,便于基于现有VS工程迁移到CMake,以及保持开源项目与现有项目的编译兼容性。
工作中的项目工程使用VisualStudio2019进行编译,而开发机已经升至最新的VisualStudio2022,故而会包含相关版本上的兼容讨论。工程配置以VisualStudioCommunity2022Preview版本为参考。
一、CMake概念与配置
参考CMake Wiki.
1. CMakeList.txt:常规CMake配置文件,配置工程名称、生成选项等,是所有生成所必须的
PROJECT(main)
CMAKE_MINIMUM_REQUIRED(VERSION 3.15)
SET(CMAKE_SOURCE_DIR .)
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
AUX_SOURCE_DIRECTORY(. DIR_SRCS)
ADD_EXECUTABLE(main ${DIR_SRCS})
2. CMAKE_BUILD_TYPE:可枚举值为Debug、Release、RelWithDebInfo、MinSizeRel
- Debug:CMake会使用CMAKE_FLAGS_DEBUG和CMAKE_C_FLAGS_DEBUG中的字符串作为编译选项生成Makefile
- Release:使用CMAKE_CXX_FLAGS_RELEASE和CMAKE_CFLAGS_RELEASE选项生成Makefile
3. CMAKE_EXE_LINKER_FLAGS:链接器标志
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /machine:x86")
4. add_dependencies:项目引用Reference,添加依赖
5. source_group( header FILES includeme.h ):Put files into folders
二、Visual Studio与CMake配置映射表
参考Windows C++ project property page reference.
三、工程配置需求的非官方非标准的解决方案
1. 设置Visual Studio的WindowsSDKVersion(WindowsTargetPlatformVersion)值为10.0 (latest installed version)
在使用cmake生成的visual studio工程中,WIndowsSDKVersion总是为本机的最新SDK版本号,对于想要控制版本号或者使用最新版本均十分不便。
查阅了现有文档,以及官方文档均无相关配置项。
可参考官方相关讨论:https://gitlab.kitware.com/cmake/cmake/-/issues/21403
在核查cmGlobalVisualStudio14Generator的代码时发现cmake有一个针对sdk版本过滤的逻辑,而visual studio默认未设置版本时是否可以自动设置为10.0呢?是的,VisualStudio会帮我们自动设置为10.0 latest installed version
具体的方式是:
cmake -G "Visual Studio 17 2022" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM=10.0
但是,这样是否有什么后遗症? 目前还未验证,待调试所有参数完毕后测试验证下编译效果
2. 推荐使用至少3.15版本的CMake
CMAKE_MINIMUM_REQUIRED(VERSION 3.15)
3. 用于执行CMake的bat脚本
使用.bat脚本调用cmake,可以指定比较复杂的cmake.exe命令的参数
:: ${ProjectRoot}/build/vs2017-x64.bat
@echo off
::build directory
:: it should be similar name with cmake generator name
set BUILD_DIR=vs2017-x64
:: platform
:: x86 or x64
set BUILD_PLATFORM=x64
:: cl.exe compiler version
set BUILD_COMPILER=v142
:: create directory if not exist
if not exist %BUILD_DIR% md %BUILD_DIR%
cd %BUILD_DIR%
:: run cmake by specifing:
:: - generator
:: - installation directory
:: - CMakeLists.txt location
cmake -G "Visual Studio 12 2017 Win64" -DCMAKE_INSTALL_PREFIX=D:/target/%BUILD_PLATFORM%/%BUILD_COMPILER%
:: run build by specifying config and target
:: note: this may fail, and please open .sln and do manual compilation and installation
cmake --build . --config Release --target INSTALL
:: go back to old folder
cd ..
:: stuck to show build messages
pause
4. 判断平台:32位、64位
方法1:CMAKE_SIZEOF_VOID_P 表示 void* 的大小(例如为 4 或者 8),可以使用其来判断当前构建为 32 位还是 64 位,CMake官方推荐
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
message(STATUE "64bit")
else()
message(STATUE "32bit")endif()
方法2:判断**CMAKE_CL_64** 是否为true,CMake官方已废弃,此判断仅且仅当使用cl.exe时有效
if(CMAKE_CL_64)
message(STATUS "MSVC 64bit")
else()
message(STATUS "MSVC 32bit")
endif()
5. 判断Visual Studio版本:MSVC_VERSION
6. 判断操作系统:其中WIN32判断的是windows系统,包括32位和64位两种情况
if(WIN32)
message(STATUS "----- This is Windows.")
elseif(UNIX)
message(STATUS "----- This is UNIX.") #Linux下输出这个
elseif(APPLE)
message(STATUS "----- This is APPLE.")
elseif(ANDROID)
message(STATUS "----- This is ANDROID.")
endif(WIN32)
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
message(STATUS "----- OS: Windows")
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
message(STATUS "----- OS: Linux")
elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
message(STATUS "----- OS: MacOS X")
elseif(CMAKE_SYSTEM_NAME MATCHES "Android")
message(STATUS "----- OS: Android")endif()
测试发现,如果在CMAKE_MINIMUM_VERSION()后立即使用CMAKE_SYSTEM_NAME,Linux下得到结果为空,Android下得到为Android。看起来是Android的toolchain中进行了设定。
7. 判断Debug/Release
- CMAKE_BUILD_TYPE取值:默认值由编译器决定,调用cmake时可通过-DCMAKE_BUILD_TYPE=Release的形式指定其值
- Debug:CMAKE_BUILD_TYPE MATCHES "Debug" OR CMAKE_BUILD_TYPE EQUAL "None" OR NOT CMAKE_BUILD_TYPE
- Release:CMAKE_BUILD_TYPE MATCHES "Release"
- RelWitchDebInfo:CMAKE_BUILD_TYPE MATCHES "RelWitchDebInfo"
- MinSizeRel:CMAKE_BUILD_TYPE MATCHES "MinSizeRel"
8. 根据Debug/Release添加不同的库目录
set(PROJECT_LIB_DIR, "path")
在vs平台下,会自动把path和path/$(Configuration)添加到库搜索目录。
9. 设定编译选项
修改CMAKE_C_FLAGS、CMAKE_CXX_FLAGS变量
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /we4013 /we4431")
10. SAFESEH报错
error LNK2026: 模块对于 SAFESEH 映像是不安全的
fatal error LNK1281: 无法生成 SAFESEH 映像
解决办法是:
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
#message("inside windows")
# add SAFESEH to Visual Studio. copied from http://www.reactos.org/pipermail/ros-diffs/2010-November/039192.html
#if(${_MACHINE_ARCH_FLAG} MATCHES X86) # fails
#message("inside that branch")
# in VS2013, there is: fatal error LNK1104: cannot open file "LIBC.lib"
# so, we have to add /NODEFAULTLIB:LIBC.LIB
# reference: https://stackoverflow.com/questions/6016649/cannot-open-file-libc-lib
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SAFESEH:NO /NODEFAULTLIB:LIBC.LIB")
set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /SAFESEH:NO /NODEFAULTLIB:LIBC.LIB")
set (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /SAFESEH:NO /NODEFAULTLIB:LIBC.LIB")
#endif()
endif (CMAKE_SYSTEM_NAME MATCHES "Windows")
11. link_directory但是链接异常
link_directories() 这句话必须在add_executable()之前写 不然找不到库目录
或者先add_executable() 再 target_link_directories(XXX PRIVATE some direcotry)
12. Debug库带“d”后缀
设置debug模式下编译出的库文件,相比于release模式下,多带一个字母"d"作为后缀。
- 对于单个目录:set_target_properties(TargetName PROPERTIES DEBUG_POSTFIX d)
- 对于整个CMakeLists.txt:set(CMAKE_DEBUG_POSTFIX d)
- 在调用cmake时临时指定:-DCMAKE_DEBUG_POSTFIX=d
13. 在cmake中执行目录创建、拷贝文件等脚本:add_custom_command、execute_process
-
创建目录:file(MAKE_DIRECTORY ${SO_OUTPUT_PATH})
-
和某个target绑定的文件拷贝,使用add_custom_command:add_custom_command(TARGET your_target PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${MY_SO_NAME} ${SO_OUTPUT_PATH}/)
-
和target无关的,或者说对于所有target而言都需要做文件拷贝,用execute_process
foreach(lib_name_pth ${LIBS_TO_COPY})
message(STATUS "--- ${lib_name_pth}")
execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${lib_name_pth} ${SO_OUTPUT_PATH})
endforeach()
14. 转换相对路径为绝对路径
get_filename_component(SO_OUTPUT_PATH_ABS ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${ANDROID_ABI} ABSOLUTE)
15. 循环处理列表:cmake中的列表也是字符串,不过,通过list(APPEND)得到的列表字符串,可以用foreach来遍历其中每个字符串
foreach(loop_var arg1 arg2 arg3)
message(STATUS "--- ${loop_var}")
endforeach(loop_var)
foreach(loop_var ${SNPE_LIB_ALL})
message(STATUS "--- ${loop_var}")
endforeach(loop_var)
16. 设置C/C++编译器
通过设定CMAKE_C_COMPILER和CMAKE_CXX_COMPILER来做到。
注意:project()命令必须在设定编译器之后出现,否则编译器的设定不起作用,将使用系统默认编译器。
if (UNIX)
message(STATUS "----- This is Linux.")
set(CMAKE_C_COMPILER "gcc-4.9")
set(CMAKE_CXX_COMPILER "g++-4.9")
endif()
project(gamma)
注:前一种方法是在单个CMakeLists.txt中设定。对于跨平台编译,则应当避免污染根CMakeLists.txt,应该为每个平台分别使用cmake cache script。而在cache script中需要设定的变量,都应该是缓存变量。
set(CMAKE_C_COMPILER gcc CACHE STRING "C compiler")
set(CMAKE_CXX_COMPILER g++ CACHE STRING "C++ compiler")
set(PLATFORM_NAME "SigmaStar" CACHE STRING "")
17. 设定导入库(IMPORTED)及其属性
如果是自己项目中的源码基于cmake构建,其中利用add_library()创建的库目标,可以直接用来作为可执行目标、动态库或静态库的依赖库直接使用。
而如果是别人直接丢过来的库和头文件、没有用cmake封装一次呢?显然我们不应该在Visual Studio的项目属性中手动添加,手写一个导入库的cmake,在add_library()命令中指定关键字IMPORTED,再用set_target_properties()命令来设定导入库目标的头文件目录、库目录、库文件名字:
add_library(rock SHARED IMPORTED GLOBAL)
set_target_properties(rock PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "inc" #PUBLIC头文件目录
IMPORTED_IMPLIB "rock.lib" #Windows平台上dll库的.lib库所在位置
IMPORTED_LOCATION "rock.dll" #dll库的.dll所在位置,或者.so库的位置,或者静态库的位置
)
其中GLOBAL关键字,是为了让全局可见。例如通过add_subdirectory()添加了mpbase库,里面是上述方式添加的库,但是上级CMakeLists.txt要确保能使用这个库,就需要指定GLOBAL关键字。
P.S. 实践发现,如果库文件所在目录很长(超过256个字符),或者添加的导入库对应的库文件有多个,它们的名字会被拼接起来,在CMake+Ninja的NDK开发环境下直接报错说路径太长。因此,导入库并不是一个好的实践。
18. 查看并修改Visual Studio项目属性中的某个设定
问题来自StackOverFlow上某网友的提问:Compile error CMAKE with CUDA on Visual Studio C++
解决步骤:
- 用cmake-gui.exe或ccmake加载cmake的cache文件
- 查找需要修改的字符串对应的CMake变量
- 在CMakeLists.txt中修改、覆盖此变量
19. 添加宏定义
add_definitions(-DUSE_OPENCV)
add_definitions(-DLANDMARK_VERSION=2.1.33)
相当于传递给C/C++编译器:
#define USE_OPENCV
#define LANDMARK_VERSION 2.1.33
20. 设置fPIE
error: Android 5.0 and later only support position-independent executables (-fPIE)
问题出现在:连接一个静态库到一个可执行程序,并在android6.0上运行
解决办法:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE -pie")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -fPIE -pie")
21. 设置fPIC
问题出现场景:编译动态库libaisf_bodyattr_processor.so的时候,它依赖于静态库libarcsoft_bsd.a,但是libarcsoft_bsd.a库编译时没有指定fPIC编译选项。
-
在编译静态库的时候,全局设定:set(CMAKE_POSITION_INDEPENDENT_CODE ON)
-
在编译静态库的时候,设定
add_library(lib1 lib1.cpp)
set_property(TARGET lib1 PROPERTY POSITION_INDEPENDENT_CODE ON)
22. Linux gcc添加链接库"-lm":target_link_libraries(xxx m)
23. 清空普通变量:unset()
24. 清除缓存变量:unset( CACHE)
25. FindXXX.cmake简单例子
使用场景满足的条件:
- 使用了很多依赖项;
- 每个依赖项仅仅提供了.a/.lib/.so库文件和.h/.hpp头文件,没有提供XXX-config.cmake脚本;
- 每个依赖项的库文件包括debug和release两种
此时如果继续在CMakeLists.txt中“一把梭”,各种设定都写在单个文件中,可以执行就不够高了。每个依赖写成FindXXX.cmake,则后续直接使用find_package(XXX)很方便,定位排查依赖项问题、可移植性都得到了增强。
FindXXX.cmake基本步骤
- 步骤1:FindXXX.cmake第一行:include(FindPackageHandleStandardArgs)
- 步骤2:想方设法设定_INCLUDE_DIRS和_LIBRARIES的值,并且避免硬编码。具体又可以包括:
- 使用set()和list(APPEND )来设定变量的值
- 使用find_library()来找库文件和头文件(比直接写为固定值要灵活)
- 步骤3:find_package_handle_standard_args( DEFAULT_MSG _INCLUDE_DIRS _LIBRARIES)
- 步骤4:根据是否查找成功进行打印
例子1:单个头文件和单个库文件
# provides `milk_LIBRARIES` and `milk_INCLUDE_DIRS` variable
# usage: `find_package(milk)`
include(FindPackageHandleStandardArgs)
set(milk_ROOT_DIR ${PROJECT_SOURCE_DIR}/third_party/milk CACHE PATH "Folder contains milk")
set(milk_DIR ${milk_ROOT_DIR})
find_path(milk_INCLUDE_DIRS
NAMES milk.h
PATHS ${milk_DIR}
PATH_SUFFIXES include include/x86_64 include/x64
DOC "milk include"
NO_DEFAULT_PATH)
# find milk.libfind_library(milk_LIBRARIES
NAMES milk
PATHS ${milk_DIR}
PATH_SUFFIXES lib lib64 lib/x86_64 lib/x86_64-linux-gnu lib/x64 lib/x86
DOC "milk library"
NO_DEFAULT_PATH)
find_package_handle_standard_args(milk DEFAULT_MSG milk_INCLUDE_DIRS milk_LIBRARIES)
if (milk_FOUND)
if (NOT milk_FIND_QUIETLY)
message(STATUS "Found milk: ${milk_INCLUDE_DIRS}, ${milk_LIBRARIES}")
endif ()
mark_as_advanced(milk_ROOT_DIR milk_INCLUDE_DIRS milk_LIBRARIES)
else ()
if (milk_FIND_REQUIRED)
message(FATAL_ERROR "Could not find milk")
endif ()
endif ()
例子2:同时存在Debug和Release版本的库
希望在调用find_package(xxx)之后,Visual Studio或XCode等IDE能自动切换debug和release的库。则需要为debug库的路径添加debug字段,为release库添加optimized字段。
set(LEMON_LIBRARIES
debug "${LEMON_DIR}/lib/debug/lemon.lib"
optimized "${LEMON_DIR}/lib/release/lemon.lib" )
考虑到硬编码不是一个好的方案,库文件可能放在lib、lib64、lib/Release等目录中,应当先用find_library()进行查找,然后再set库文件变量LEMON_LIBRARIES。
多个库的find_library写法
对于依赖库中的多个库,自然的想法是使用foreach()来处理每个库文件。
考虑到find_library(lemon_lib_name)会产生缓存变量lemon_lib_name,这会导致再次调用find_library(lemon_lib_name)时不再查找。需要unset(${lemon_lib_name} CACHE)该缓存变量来确保查找成功。直接给出完整例子:
include(FindPackageHandleStandardArgs)
set(LEMON_ROOT_DIR ${PROJECT_SOURCE_DIR}/third_party/lemon CACHE PATH "Folder contains lemon")
set(LEMON_DIR ${CEVA_ROOT_DIR})
set(LEMON_DIR ${LEMON_ROOT_DIR})
set(LEMON_LIBRARY_COMPONENTS lemon_core lemon_extra)
foreach(lemon_component ${LEMON_LIBRARY_COMPONENTS})
unset(LEMON_LIBRARIES_DEBUG CACHE)
find_library(LEMON_LIBRARIES_DEBUG
NAMES ${lemon_component}
PATHS ${LEMON_DIR}
PATH_SUFFIXES lib lib/debug lib/debug
DOC "lemon library component ${lemon_component} debug"
NO_DEFAULT_PATH)
unset(LEMON_LIBRARIES_RELEASE CACHE)
find_library(LEMON_LIBRARIES_RELEASE
NAMES ${lemon_component}
PATHS ${LEMON_DIR}
PATH_SUFFIXES lib lib/release lib/Release
DOC "lemon library component ${lemon_component} release"
NO_DEFAULT_PATH)
list(APPEND LEMON_LIBRARIES
debug ${LEMON_LIBRARIES_DEBUG}
optimized ${LEMON_LIBRARIES_RELEASE}
)
endforeach()
find_package_handle_standard_args(LEMON DEFAULT_MSG LEMON_LIBRARIES)
if (LEMON_FOUND)
if (NOT LEMON_FIND_QUIETLY)
message(STATUS "Found LEMON: ${LEMON_LIBRARIES}")
endif ()
mark_as_advanced(LEMON_ROOT_DIR LEMON_LIBRARIES)
else ()
if (LEMON_FIND_REQUIRED)
message(FATAL_ERROR "Could not find lemon")
endif ()
endif ()
例子3:找dll
注意:CMAKE_FIND_LIBRARY_SUFFIXES的使用:CMake find_library matching behavior?
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
实际上,CMAKE_FIND_LIBRARY_SUFFIXES影响最大的就是find_library()命令了。譬如zlib安装目录下,同时存在动态库和静态库,分别是libz.a和libz.so,而find_library()的行为是“找到一个就不再找了”,因此如果没有很好的设定CMAKE_FIND_LIBRARY_SUFFIXES,就会导致找不到想要的库。默认情况下是找到动态库,然而windows下还需要手动拷贝DLL。。。麻烦。
可以通过备份原有的CMAKE_FIND_LIBRARY_SUFFIXES的值,改掉它的值,find_library()之后再改回原来的值,这样就支持了 静态库/动态库 分别查找的设定。。(用于魔改cmake自带的FindZLIB.cmake)
CMake find module to distinguish shared or static library
26. CMake各种编译链接参数的默认值
cmake_minimum_required(VERSION 3.2)
message(STATUS "CMAKE_C_FLAGS = " ${CMAKE_C_FLAGS})
message(STATUS "CMAKE_C_FLAGS_DEBUG = " ${CMAKE_C_FLAGS_DEBUG})
message(STATUS "CMAKE_C_FLAGS_MINSIZEREL = " ${CMAKE_C_FLAGS_MINSIZEREL})
message(STATUS "CMAKE_C_FLAGS_RELEASE = " ${CMAKE_C_FLAGS_RELEASE})
message(STATUS "CMAKE_C_FLAGS_RELWITHDEBINFO = " ${CMAKE_C_FLAGS_RELWITHDEBINFO})
message(STATUS "CMAKE_CXX_FLAGS = " ${CMAKE_CXX_FLAGS})
message(STATUS "CMAKE_CXX_FLAGS_DEBUG = " ${CMAKE_CXX_FLAGS_DEBUG})
message(STATUS "CMAKE_CXX_FLAGS_MINSIZEREL = " ${CMAKE_CXX_FLAGS_MINSIZEREL})
message(STATUS "CMAKE_CXX_FLAGS_RELEASE = " ${CMAKE_CXX_FLAGS_RELEASE})
message(STATUS "CMAKE_CXX_FLAGS_RELWITHDEBINFO = " ${CMAKE_CXX_FLAGS_RELWITHDEBINFO})
message(STATUS "CMAKE_EXE_LINKER_FLAGS = " ${CMAKE_EXE_LINKER_FLAGS})
message(STATUS "CMAKE_EXE_LINKER_FLAGS_DEBUG = " ${CMAKE_EXE_LINKER_FLAGS_DEBUG})
message(STATUS "CMAKE_EXE_LINKER_FLAGS_MINSIZEREL = " ${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL})
message(STATUS "CMAKE_EXE_LINKER_FLAGS_RELEASE = " ${CMAKE_EXE_LINKER_FLAGS_RELEASE})
message(STATUS "CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO = " ${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO})
message(STATUS "CMAKE_MODULE_LINKER_FLAGS = " ${CMAKE_MODULE_LINKER_FLAGS})
message(STATUS "CMAKE_MODULE_LINKER_FLAGS_DEBUG = " ${CMAKE_MODULE_LINKER_FLAGS_DEBUG})
message(STATUS "CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL = " ${CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL})
message(STATUS "CMAKE_MODULE_LINKER_FLAGS_RELEASE = " ${CMAKE_MODULE_LINKER_FLAGS_RELEASE})
message(STATUS "CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO = " ${CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO})
message(STATUS "CMAKE_SHARED_LINKER_FLAGS = " ${CMAKE_SHARED_LINKER_FLAGS})
message(STATUS "CMAKE_SHARED_LINKER_FLAGS_DEBUG = " ${CMAKE_SHARED_LINKER_FLAGS_DEBUG})
message(STATUS "CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL = " ${CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL})
message(STATUS "CMAKE_SHARED_LINKER_FLAGS_RELEASE = " ${CMAKE_SHARED_LINKER_FLAGS_RELEASE})
message(STATUS "CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO = " ${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO})
message(STATUS "CMAKE_STATIC_LINKER_FLAGS = " ${CMAKE_STATIC_LINKER_FLAGS})
message(STATUS "CMAKE_STATIC_LINKER_FLAGS_DEBUG = " ${CMAKE_STATIC_LINKER_FLAGS_DEBUG})
message(STATUS "CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL = " ${CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL})
message(STATUS "CMAKE_STATIC_LINKER_FLAGS_RELEASE = " ${CMAKE_STATIC_LINKER_FLAGS_RELEASE})
message(STATUS "CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO = " ${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO}
27. 链接器相关问题
检查链接到的重名函数
场景:A库的代码中定义了函数play(),B库的代码中也定义了函数play(),但是这两个play()函数的实现不同,并且被可执行目标C同时链接。
链接器默认是找到一个符号就不再查找,因此默认能链接并且可以运行,只不过运行结果不是所期待的。
容易查到,Linux下gcc对应的链接器中可以使用--whole-archive和--no-whole-archive参数来包含静态库中的所有符号。
如果是gcc,则使用gcc -Wl --whole-archive someLib --no-whole-archive。
如果是Visual Studio,则需要>=2015 update2的版本中才支持/WHOLEARCHIVE选项,VS2013要哭泣了。
因而,在CMakeLists.txt中,可以设定链接器的全局设定:
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /WHOLEARCHIVE")
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--whole-archive")
endif()
缺点:
- 所有库的符号都进行导入,不能灵活处理单个不需要导入所有符号的库
- 系统默认导入的库,例如Windows下的USER32.dll和KERNEL32.dll会产生冲突
TODO: 对于单个target,如何设定?
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
set_target_properties(inter
PROPERTIES LINK_FLAGS
"/WHOLEARCHIVE:gender /WHOLEARCHIVE:smile"
)
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
# 不起作用
set_target_properties(inter
PROPERTIES LINK_FLAGS
"-Wl,--whole-archive gender
-Wl,--whole-archive smile"
)
endif()
或者:
set(MYLIB -Wl,--whole-archive mytest -Wl,--no-whole-archive)
target_link_libraries(main ${MYLIB})
实际上:
-
gcc的链接器ld:通过-Wl, --whole-archive lib_name -Wl, --no-whole-archive能每次分别对一个静态库加载所有的member(函数等
-
Visual Studio的链接器:VS2017(1900)之后才支持-WHOLEARCHIVE来实现同样功能;
-
PC Clang的链接器:使用lld作为链接器(比如Xcode现在用肯定是lld),用-Wl,-force_load ${lib}来做到只导入一个静态库中的所有member
-
NDK的链接器:怎么说现在用的NDK也是17b起步了,默认编译器是Clang,gcc暂时没考虑;虽然编译器很早就(可以)切换到Clang了,但链接器目前还是用的gcc的ld,因此NDK的链接阶段检查重复符号应该用ld的检查方式;即使是用当前(2020-01-26 02:16:34)最新的NDK也就是NDK21,手动传入ANDROID_LD=lld后,NDK切换到的链接器lld和MacOS上与AppleClang搭配的lld也还是不一样,链接阶段查重复符号仍然需要传gcc的ld的那一套参数:-Wl, --whole-archive lib_name -Wl, --no-whole-archive,但好处是报错界面更加友好直观了:
28. 生成compile_commands.json:用于在VSCode等编辑器/IDE中给C/C++代码做函数定义跳转支持
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
29. 子目录CMakeLists.txt中产生变量给父目录中的CMakeLists.txt使用
set设定变量并且设定PARENT_SCOPE参数。当项目中代码变多,就可能需要分成多个目录存放。每个目录下放一个CMakeLists.txt,写出它要处理的文件列表,然后暴露给外层CMakeLists.txt,使外层CMakeLists.txt保持清爽结构。
set(hello_srcs
${CMAKE_CURRENT_SOURCE_DIR}/hello.cpp)
set(hello_private_incs
${CMAKE_CURRENT_SOURCE_DIR}/hello.h)
set(hello_srcs ${hello_srcs} PARENT_SCOPE)
set(hello_private_incs ${hello_private_incs} PARENT_SCOPE)
30. 在IDE中将targets分组显示:使用folder
包括两步:
- 在最顶部的CMakeLists.txt中添加一句:set_property(GLOBAL PROPERTY USE_FOLDERS ON)
- 在希望出现在文件夹的项目的add_library或add_executable后添加:set_property(TARGET ${prj_target} PROPERTY FOLDER Samples)
31. 设置Debug的优化级别参数
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
32. cmake生成VS2019的工程
VS2019开始,需要用-A参数指定是32位程序,还是64位程序。以前的用法不行了
cmake -G "Visual Studio 16 2019" -A Win32 ..\..
cmake -G "Visual Studio 16 2019" -A x64 ..\..
33. 不要同时使用include_directories()和target_include_directories()
include_directories("inc" "src")
add_library(rock src/rock.cpp)
target_include_directories(rock PRIVATE "3rdparty/spdlog")
用法有坑,其中target_include_directories()设置的目录不会生效。经验:只使用其中一种设定include目录的方式。
34. NDK开发中出现error: cannot use 'try' with exceptions disabled
这需要编译器的编译选项中开启exception的支持。现在NDK开发,主流的做法是Android Studio + gradle + cmake做构建,需要在build.gradle中设定-fexceptions:
externalNativeBuild {
cmake
{// cppFlags '-std=c++11 -fexceptions'
// arguments '-DANDROID_PLATFORM=android-21',
// '-DANDROID_TOOLCHAIN=clang',
// '-DCMAKE_BUILD_TYPE="Release',
// '-DANDROID_ARM_NEON=ON',
// '-DANDROID_STL=c++_shared'
// cppFlags '-std=c++11'
// arguments '-DANDROID_TOOLCHAIN=clang',
// '-DANDROID_STL=c++_static'
cppFlags "-std=c++11 -fexceptions"
}
}
但有时候,发现上述设定后并不生效;尝试删除.externalBuild目录重新构建,仍然报exception无法处理。后来发现,问题出在CMakeLists.txt加载的.cmake脚本中(用的代码框架是其它部门同事写的,所以不是很熟悉),他给手动设定了这么一句:
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions -fno-short-enums -Werror=non-virtual-dtor")
去掉-fno-exception即可。
35. cmake-gui中可见(可检索)的变量
目前遇到的有两种:
- option( )命令定义的变量:option(USE_OPENCV “use opencv?” CACHE PAH)
- 缓存变量:set(varName "value" CACHE STRING "")
当然,还可以使用mark_as_adances来设定,则默认在cmake-gui中不可见
36. Ninja error: Filename longer than 260 characters
NDK开发中,CMake+Ninja构建,如果文件名超过260个字符会失败。这个限制略蛋疼
ninja: error: Stat(../../deps/arcsoft_landmark/2.1.12021.33/libs/Android/armeabi-v7a/libarc_net_sgl.a;E:/dbg/myy/landmark_demo/deps/arcsoft_landmark/2.1.12021.33/libs/Android/armeabi-v7a/libarcsoft_face_detection.a;E:/dbg/myy/landmark_demo/deps/arcsoft_landmark/2.1.12021.33/libs/Android/armeabi-v7a/libarcsoft_landmark_tracking.a;E:/dbg/myy/landmark_demo/deps/arcsoft_landmark/2.1.12021.33/libs/Android/armeabi-v7a/libarcsoft_videooutline.a): Filename longer than 260 characters
37. cmake判断C/C++编译器
比如我想要定制一些安全的编译选项,发现需要区分msvc,gcc和clang。容易直接想到CMAKE_C_COMPILER和CMAKE_CXX_COMPILER。但考虑到-G Xcode和命令行下分别输出,得到的结果并不都是clang,这就很蛋疼。
使用CMAKE_CXX_COMPILER_ID比较方便,像上面提到的case会做合并输出AppleClang。而如果是NDK-r17c则输出Clang。
常见的平台下对应输出:
- MSVC
- Clang
- GNU
- AppleClang
更多结果看官方文档 CMAKE__COMPILER_ID
38. cmake字符串追加元素,并且用空格分隔
简单有效的两种方式:
- set: set(MY_FLAGS "${MY_FLAGS} -Werror=shadow")
- 封装一个函数
function(afq_list_append __string __element)
set(${__string} "${${__string}} ${__element}" PARENT_SCOPE)
endfunction()
39. cmake --build的使用:cmake执行编译链接、安装
本质上,当使用cmake ..,或cmake -G "Visual Studio 15 2017 Win64" ../..类似命令时,是执行“pre-make”,相当于是makefile的生成器,可以说对于你的项目代码来说并没执行编译链接,更没有安装。
实际使用经验:cmake生成了这些cache文件后,可以打开Visual Studio编译,或执行make编译(Linux下)。但这些都是native tool。通用的方式则是用cmake包装好的接口:
# 执行编译(如果是可执target,则包括链接过程)
cmake --build . --config Release
# 执行某个target的编译(如果是可执target,则包括链接过程)
cmake --build . --config Release --target xx
# 执行安装
cmake --install . --prefix d:/lib/openblas/clang-cl/x64 -v
40. C/C++和汇编混编
在cmake里混合编译C/C++与汇编代码,通过enable_language(ASM_)可以做到。
例如x86(32位)的MASM(Visual Studio支持的一种汇编语法):enable_language(ASM_MASM)
此外往往还需要给Visual Studio设置SEH:
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SAFESEH:NO /NODEFAULTLIB:LIBC.LIB") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /SAFESEH:NO /NODEFAULTLIB:LIBC.LIB")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /SAFESEH:NO /NODEFAULTLIB:LIBC.LIB")
汇编文件则和C/C++文件一起,正常添加为target的依赖即可:add_executable(run src/main.cpp src/CalcSum_.asm)
41. cmake设置pthread
- target_link_libraries(xxx pthread):有时候不管用(例如ARM Android平台),则用下面的方式
- find_package( Threads ) && target_link_libraries( testbed ${CMAKE_THREAD_LIBS_INIT} log)
- 注意ARM Android(也就是通常说的NDK开发中),需要给cmake传一个选项:-DANDROID_PLATFORM=android-24 ^(我是armv8所以用24)
42. cmake使用pkg-config
有些依赖库只提供.pc文件,甚至已经配置了CMake脚本但是安装后还是只有.pc而没有XXXConfig.cmake或xxx-config.cmake。并且不仅是Linux,Windows上也这样。
这就不得不在CMake中尝试去加载.pc文件。原理是,cmake里面封装了对pkg-config工具的兼容,可以认为是一个插件,用这个插件去加载.pc文件。实际测试发现Linux和Windows都可以用。
- Linux安装pkg-config:sudo apt install pkg-config
- Windows安装pkg-config-lite
使用:先找到xx.pc文件,然后分成目录和文件前缀两部分,在cmake中配置
举例1:Ubuntu 16.04下用apt安装openblas并在CMake中用pkg-config方式配置:
cmake_minimum_required(VERSION 3.15)
project(cmake_pkg_config_example)
set(ENV{PKG_CONFIG_PATH} /usr/lib/pkgconfig)
find_package(PkgConfig)
pkg_search_module(OBS REQUIRED blas-openblas)
message(STATUS "=== OBS_LIBRARIES: ${OBS_LIBRARIES}")
message(STATUS "=== OBS_INCLUDE_DIRS: ${OBS_INCLUDE_DIRS}")
举例2:Windows 10下用CMake配置Pangolin安装中配置的zlib
cmake_minimum_required(VERSION 3.15)
project(cmake_pkg_config_example)
#指定pkg-config.exe绝对路径
set(PKG_CONFIG_EXECUTABLE "D:/soft/pkg-config/bin/pkg-config.exe")
#指定zlib.pc所在目录
set(ENV{PKG_CONFIG_PATH} "D:/lib/pangolin/share/pkgconfig")
find_package(PkgConfig)
message(STATUS "--- PKG_CONFIG_FOUND: ${PKG_CONFIG_FOUND}")
message(STATUS "--- PKG_CONFIG_VERSION_STRING: ${PKG_CONFIG_VERSION_STRING}")
pkg_search_module(ZLIB REQUIRED zlib)
message(STATUS "=== ZLIB_LIBRARIES: ${ZLIB_LIBRARIES}")
message(STATUS "=== ZLIB_INCLUDE_DIRS: ${ZLIB_INCLUDE_DIRS}")
43. cmake多行注释
#[[
第一种注释方式
#]]
#[===============[
第二种注释方式
#]===============]
44. 命令行-D指定参数
在cmake脚本中指定其中任意一种:
- cmake缓存类型的变量:set(CAFFE_TARGET_VERSION "1.0.0" CACHE STRING "Caffe logical version")
- option:option(USE_OPENCV "Do we use OpenCV?" ON)
45. include()指令
- 包含文件,例如include(utils.cmake)
- 包含模块,在CMAKE_MODULE_PATH->CMAKE MODULE DIRECTORY依次查找。例如include(ExternalModule),会在这两个路径列表中查找ExternalModule.cmake文件并加载
CMAKE_MODULE_PATH是在CMake脚本中用户可以自行修改的变量。
CMAKE MODULE DIRECTORY是什么,官方文档没明确说。其实说的应该是cmake安装后的Modules目录,例如/usr/local/share/cmake/Modules
46. list追加元素,或list首部插入元素
- 追加(链表尾部插入):list(APPEND CMAKE_MODULE_PATH "cmake/Modules")
- 首部插入(链表首元素前插入):list(INSERT CMAKE_PREFIX_PATH 0 "$ENV{HOME}/soft/opencv/build")
47. cmake中使用IWYU
IWYU 是 google 的开源项目,用来移除不必要的头文件。
48. target_link_libraries()
cmake 3.13 开始提供的命令。低版本cmake无法使用
49. CMake构建NDK项目提示asm/types.h找不到
用CMake构建NDK项目时,会传入toolchain的cmake脚本文件android.toolchain.cmake给CMake。这个文件中会做若干设定,其中就包括include路径。
我遇到的情况是,自己手动修改CMAKE_C_FLAGS和CMAKE_CXX_FLAGS时,覆盖了它们原有的(android.toolchain.cmake修改后的)值,导致asm/types.h找不到。
# 我的错误设定:
set(CMAKE_C_FLAGS "${MY_CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${MY_CMAKE_CXX_FLAGS}")
# 正确做法应该是追加内容而非修改:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MY_CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MY_CMAKE_CXX_FLAGS}")
P.S. 排查方法:由于我是基于ninja构建的(cmake+ndk的组合下,现在通常用ninja),通过对比”能正常构建的工程“和”提示asm/types.h找不到的工程“之间${CMAKE_BINARY_DIR}目录下的rules.ninja和build.ninja来发现问题所在。
50. windows下创建的共享库,没生成.lib文件
.lib是导入库,里面存访对外可见(暴露)的符号(函数、变量)。.dll应该搭配一个.lib导入库才能使用。
如果是自己的源码生成的dll共享库,则在CMakeLists.txt一开始,添加:set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)则可以导出所有的符号。
CMake linking against shared library on windows: error about not finding .lib file
而如果只想导出一部分符号,则可以为每个函数分别指定导出规则。
51. 拷贝dll
在Windows下,Visual Studio中,如果用了动态库(例如opencv、zlib等),需要把dll放到PATH环境变量中,使得运行时能找到dll。
而其实Windows下的PATH查找,是会在CMAKE_BINARY_DIR目录下查找的。如果不想改PATH环境变量,也不希望每次都要手动拷贝dll,包括清掉build目录后重新构建时也不想手动拷贝,那么可以用cmake命令来搞。
举个例子,调用zlib库执行文本压缩解压,用到了zlib1.dll,其中executable target名字是demo。
zlib的二进制包下载:https://nsis.sourceforge.io/mediawiki/images/b/bb/Zlib-1.2.8-win64-AMD64.zip
zlib的调用示例代码:https://blog.csdn.net/yuhuqiao/article/details/82188963
cmake中拷贝zlib1.dll的写法:
# each time the `demo` target is built, we copy zlib1.dll if it is changed.
add_custom_command(TARGET demo
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${ZLIB_DLL}
${CMAKE_BINARY_DIR}/)
52. 压缩或解压.zip/tar.gz
tar命令是从cmake 3.2开始支持的内置命令,以解压doctest.zip到项目根目录为例:
execute_process(
COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/doctest.zip
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
53. 列出所有target
有些基于cmake的项目,CMakeLists.txt写的很复杂很庞大。可以列出所有target,帮助理清思路。
列出makefile中的所有target:
cd build
cmake --build . --target help
实际上,makefile里诸如"all"和"clean"这样的target,并不是我们感兴趣的。还是shell大法拼凑一下吧:
cd ~/work/my_project
mkdir build && cd build && cmake ..
make -j4 > log.txt 2>&1
grep 'Built target' log.txt | awk '{print $4}'
54. 判断文件是否存在
EXISTS可以判断,同时适用于文件和目录。
举例:查找当前目录下是否存在doctest目录,并且检查doctest目录下是否存在doctest_fwd.h和doctest.cpp文件:
if (EXISTS "${CMAKE_SOURCE_DIR}/doctest"
AND EXISTS "${CMAKE_SOURCE_DIR}/doctest/doctest_fwd.h"
AND EXISTS "${CMAKE_SOURCE_DIR}/doctest/doctest.cpp")
message(STATUS "--- doctest source code ready !")
else()
message(STATUS "--- extracting doctest source code from zip...")
execute_process(
COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/doctest.zip
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
endif()
55. 打印变量
message(STATUS "--- CMAKE_CXX_COMPILER is: ${CMAKE_CXX_COMPILER}")
include(CMakePrintHelpers)
cmake_print_variables(CMAKE_CXX_COMPILER)
56. 判断是否为目录
用IS_DIRECTORY命令判断。例如shadow中判断各个backend,如果是目录,则添加subdirectory,写法如下:
foreach (backend_dir ${backends_dir})
if (IS_DIRECTORY ${backend_dir})
add_subdirectory(${backend_dir})
endif()
endforeach()
57. Visual Studio环境下的MT,MTd, MD, MDd的设定
https://stackoverflow.com/a/56490614/2999096
从cmake3.15开始,可以用CMAKE_MSVC_RUNTIME_LIBRARY和MSVC_RUNTIME_LIBRARY设定。
-
可以全局设定:set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDebug)
-
针对单个目标设定:
add_executable(foo foo.c)
set_property(TARGET foo PROPERTY
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
候选值有4种:
- MultiThreaded:Compile with -MT or equivalent flag(s) to use a multi-threaded statically-linked runtime library.
- MultiThreadedDLL:Compile with -MD or equivalent flag(s) to use a multi-threaded dynamically-linked runtime library.
- MultiThreadedDebug:Compile with -MTd or equivalent flag(s) to use a multi-threaded statically-linked runtime library.
- MultiThreadedDebugDLL:Compile with -MDd or equivalent flag(s) to use a multi-threaded dynamically-linked runtime library.
58. 设定Address Sanitizer(ASAN)
首先确保有符号信息(例如CMAKE_BUILD_TYPE设定为Debug)。
其次是设定如下几个变量中的其中一个(多个也行但没必要,看你需求):
- CMAKE_EXE_LINKER_FLAGS
- CMAKE_EXE_LINKER_FLAGS_DEBUG
- CMAKE_SHARED_LINKER_FLAGS
- CMAKE_SHARED_LINKER_FLAGS_DEBUG
例如我编译的是可执行目标,那么:
set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
对于可执行目标,并且依赖于静态库或动态库,懒人用法:
set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
注意:ASAN似乎对vector等容器的支持不够好。对于vector,预先分配多少内存,似乎ASAN并不知道,导致vector被clear后再使用,(做下标访问一段时间后)出现的segfault,没被ASAN检测到。
59. Linux下编译32位程序
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32")
60. 设置VS的可执行文件生成目录
假设.sln目录在 build/vs2019-x64 下,则默认的可执行文件生成目录是 build/vs2019-x64/Debug 或 build/vs2019-x64/Release;而如果文件放在这一默认生成目录下的话无法被读取到(不加前缀的情况),需要放在 build/vs2019-x64 目录下才能读到。
以至于,代码里经常要根据 _MSC_VER 或者 ANDROID 等平台相关的宏,设定不同的路径,例如:
#if _MSC_VER
const char* image_path = "E:/share/to_zcx/ncnn_vk_dbg/build/vs2019-x64/ncnn_input_cpu.bmp";
#elif ANDROID
const char* image_path = "ncnn_input_cpu.bmp";
#endif
这让代码不整洁,也增加了出错的可能。
可以通过上面两截图中的方式,在项目属性中修改可执行文件的生成目录和启动目录,但仍然不方便;最好的办法还是在 CMakeLists.txt 里设定:
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_SOURCE_DIR}/bin>)
add_executable(testbed hello.cpp)
set_target_properties(testbed PROPERTIES
VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/bin")
https://stackoverflow.com/questions/47175912/using-cmake-how-to-stop-the-debug-and-release-subdirectories
https://stackoverflow.com/questions/41864259/how-to-set-working-directory-for-visual-studio-2017-rc-cmake-project
61. find_package等的debug输出
从cmake3.17开始,文档里正式说明支持CMAKE_FIND_DEBUG_MODE这一cmake变量,设定为TRUE则打印find_package/find_program/find_file等函数的打印过程
set(CMAKE_FIND_DEBUG_MODE TRUE)
find_package(...)
set(CMAKE_FIND_DEBUG_MODE FALSE)
实际上据网友反馈,稍早的版本也可以用这一变量,只不过文档里当时没写。
另外,设定CMAKE_FIND_DEBUG_MODE变量为TRUE,等价于调用cmake时候指定--debug-find参数。
如果你是cmake-GUI方式构建,菜单栏也可以选择输出debug信息:
62. 命令行方式传入option的坑
场景:命令行方式调用cmake,指定了很多option和cache variable的值,希望把这些option和cache variable放在.txt文件中,然后通过cat option.txt方式传给cmake。
结论:option.txt里的写法,-D之后不能有空格,否则无法生效。
例如,正确写法是:
-DUSE_X1=ON
-DUSE_X2=ON
-DUSE_X3=ON
错误写法是
-D USE_X1=ON
-D USE_X2=ON
-D USE_X3=ON
可以用如下 CMakeLists.txt 验证结论:
cmake_minimum_required(VERSION 3.15)
project(x)
option(USE_X1 "USE X1?" OFF)
option(USE_X2 "USE_X2?" OFF)
option(USE_X3 "USE_X3?" OFF)
if (USE_X1)
message(STATUS "USE_X1: ON")
else()
message(STATUS "USE_X1: OFF")
endif()
if (USE_X2)
message(STATUS "USE_X2: ON")
else()
message(STATUS "USE_X2: OFF")
endif()
if (USE_X3)
message(STATUS "USE_X3: ON")
else()
message(STATUS "USE_X3: OFF")
endif()
63. PowerShell中从txt读内容传给cmake
- bash里头的做法:
cmake `cat options.txt`
- powershell里头,不能用``符号,也没有cat命令。作为替代,用的是$(type options.txt),例如:
cmake G:/dev/opencv-build/mock -G "Visual Studio 16 2019" -A x64 -DCMAKE_INSTALL_PREFIX=install $(type G:/dev/opencv-build/mock/options.txt)
powershell是基于ksh语法修改而来(而不是M的别的其他的语法);‘(cat options.txt)`在bash里也能用;
两个``符号,英文正式名字叫做backtick,而不是"reverse quote"。
99. 现代CMake
常用主要有如下几个不响应target的全局设定:
-
target_compile_definitions(): 目标添加编译器编译选项,例如target_compile_definitions(shadow_jni PRIVATE -DUSE_STB -DUSE_ARM)
-
target_include_directories():目标添加包含文件,例如target_include_directories(shadow_jni PRIVATE "shadow_jni/body_detection" "shadow_jni/util" ${SNPE_INC_DIR})
-
target_link_directories():目标添加链接库查找目录,例如target_link_directories(shadow_jni PRIVATE ${SNPE_LIB_DIR})
-
target_link_libraries():目标添加链接库,例如target_link_libraries(shadow_jni ${log-lib} ${graph-lib} ${SNPE_LIB})
-
https://stackoverflow.com/questions/66485987/what-is-the-bash-reverse-quote-equivalent-in-powershell
-
https://stackoverflow.com/questions/434038/whats-the-cmd-powershell-equivalent-of-back-tick-on-bash
参考
- [1] CMAKE修改VS大总结
- [2] cmake 备忘录
- [3] CMake 手册详解(二)