目录html
link_directory()
可是连接不到库compile_commands.json
这列是我平常写CMake总结的一些代码片断,不保证如今写的全对,可是会不断更新,包扩增长新内容和修改旧有的错误内容。react
last update: 2019-7-20 21:36:01linux
使用.bat脚本调用cmake,能够指定比较复杂的cmake.exe命令的参数。android
e.g. 项目根目录/build/vs2017-x64.bat
,内容:git
@echo off :: build directory :: it should be similar name with cmake generator name set BUILD_DIR=vs2013-x64 :: platform :: x86 or x64 set BUILD_PLATFORM=x64 :: cl.exe compiler version set BUILD_COMPILER=vc12 :: create directory if not exist if not exist %BUILD_DIR% md %BUILD_DIR% :: go to build directory cd %BUILD_DIR% :: run cmake by specifing: :: - generator :: - installation directory :: - CMakeLists.txt location cmake -G "Visual Studio 12 2013 Win64" ^ -DCMAKE_INSTALL_PREFIX=D:/lib/glfw/3.3/%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
################################################## # platform ################################################## if(CMAKE_CL_64) set(platform x64) else(CMAKE_CL_64) set(platform x86) endif(CMAKE_CL_64) message(STATUS "----- platform is: ${platform}")
参考: List of _MSC_VER and _MSC_FULL_VERgithub
################################################## # visual studio version # if(MSVC_VERSION EQUAL 1600) set(vs_version vs2010) set(vc_version vc10) elseif(MSVC_VERSION EQUAL 1700) set(vs_version vs2012) set(vc_version vc11) elseif(MSVC_VERSION EQUAL 1800) set(vs_version vs2013) set(vc_version vc12) elseif(MSVC_VERSION EQUAL 1900) set(vs_version vs2015) set(vc_version vc14) elseif(MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS_EQUAL 1920) set(vs_version vs2017) set(vc_version vc15) elseif(MSVC_VERSION GREATER_EQUAL 1920) set(vs_version vs2019) set(vc_version vc15) endif() message(STATUS "----- vs_version is: ${vs_version}") message(STATUS "----- vc_version is: ${vc_version}")
注:其中WIN32判断的是windows系统,包括32位和64位两种状况json
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)
另外一种写法:ubuntu
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中进行了设定。windows
(1) CMAKE_BUILD_TYPE
取值:默认值由编译器决定,调用cmake时可经过-DCMAKE_BUILD_TYPE=Release
的形式指定其值。缓存
看文档的话,是用CMAKE_BUILD_TYPE
判断Debug/Release模式。然而CMake文档的描述其实有问题,不清晰。这个变量的值是由编译器决定的。对于VS2017,默认状况下为空。
The default will be "empty" or "Debug" depending on the compiler. The value of the variable will be only of interest in places where SOME_VAR_${CONFIG} is used. So to answer your question. From my understanding the debug flags could be added. The documentation (http://www.cmake.org/cmake/help/v3.3/variable/CMAKE_BUILD_TYPE.html) is unfortunately not so clear
ref: What happens for C/C++ builds if CMAKE_BUILD_TYPE is empty?
(2) 值的判断
Debug和Release,用MATCHES判断;
空值""用NOT判断.
if (CMAKE_BUILD_TYPE MATCHES "Debug" OR CMAKE_BUILD_TYPE EQUAL "None" OR NOT CMAKE_BUILD_TYPE) message(STATUS "----- CMAKE_BUILD_TYPE is Debug") elseif (CMAKE_BUILD_TYPE MATCHES "Release") message(STATUS "----- CMAKE_BUILD_TYPE is Release") elseif (CMAKE_BUILD_TYPE MATCHES "RelWitchDebInfo") message(STATUS "----- CMAKE_BUILD_TYPE is RelWitchDebInfo") elseif (CMAKE_BUILD_TYPE MATCHES "MinSizeRel") message(STATUS "----- CMAKE_BUILD_TYPE is MinSizeRel") else () message(STATUS "----- unknown CMAKE_BUILD_TYPE = " ${CMAKE_BUILD_TYPE}) endif ()
在Visual Studio平台下测试发现,若是指定了A目录到库搜索目录,而且A目录下有名为Debug/Release的目录,则会自动把A/Debug和A/Release添加到库搜索目录。
set(MY_LIB_DIR "testbed/lib/" "testbed/lib/ceva/" )
于是,至少在VS平台下,不须要手动根据Debug和Release来分别添加库。
也即修改CMAKE_C_FLAGS
、CMAKE_CXX_FLAGS
变量。例如追加两个选项:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /we4013 /we4431")
简单粗暴的解决办法:
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")
link_directory()
可是连接不到库link_directories() 这句话必须在add_executable()以前写 否则找不到库目录
或者先add_executable() 再 target_link_directories(XXX PRIVATE some direcotry)
设置debug模式下编译出的库文件,相比于release模式下,多带一个字母"d"做为后缀。
set_target_properties(TargetName PROPERTIES DEBUG_POSTFIX d)
例如建立完调用lmdb库的可执行程序这一target后,须要建立目录。不想每次都手动去建立一个目录,但愿在CMakeLists.txt中添加这个操做。
尝试了add_custom_command()
,不怎么会用。。没有效果,不被调用。
用execute_process()
则是能够的,例如:
execute_process(COMMAND echo "=====")
https://moevis.github.io/cheatsheet/2018/09/12/Modern-CMake-%E7%AC%94%E8%AE%B0.html
https://cliutils.gitlab.io/modern-cmake/modern-cmake.pdf
https://cliutils.gitlab.io/modern-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})
看起来不是很好学的样子。
file(MAKE_DIRECTORY ${SO_OUTPUT_PATH})
ref: Creating a directory in CMake
包括两种类型:
(1)和某个target绑定的文件拷贝,使用add_custom_command()
;
add_custom_command(TARGET your_target PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${MY_SO_NAME} ${SO_OUTPUT_PATH}/ )
(2)和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()
get_filename_component(SO_OUTPUT_PATH_ABS ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${ANDROID_ABI} ABSOLUTE)
ref: CMake: Convert relative path to absolute path, with build directory as current directory
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)
例如在Linux须要切换gcc/g++版本,ubuntu16.04默认是gcc/g++ 5.4,SNPE须要gcc/g++ 4.9。
经过设定CMAKE_C_COMPILER
和CMAKE_CXX_COMPILER
来作到。
注意:project(<ProjName>)
命令必须在设定编译器以后出现,不然编译器的设定不起做用,将使用系统默认编译器。
if (UNIX) message(STATUS "----- This is Linux.") set(CMAKE_C_COMPILER "gcc-4.9") set(CMAKE_CXX_COMPILER "g++-4.9") endif() project(gamma)
问题来自StackOverFlow上某网友的提问:Compile error CMAKE with CUDA on Visual Studio C++
解决步骤:
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")
target_link_libraries(xxx m)
unset(<Var>)
unset(<Var> CACHE)
我遇到的使用场景知足的条件:
XXX-config.cmake
脚本;此时若是继续在CMakeLists.txt中“一把梭”,各类设定都写在单个文件中,能够执行就不够高了。每一个依赖写成FindXXX.cmake
,则后续直接使用find_package(XXX)
很方便,定位排查依赖项问题、可移植性都获得了加强。
步骤1:FindXXX.cmake第一行:include(FindPackageHandleStandardArgs)
步骤2:千方百计设定<PackageName>_INCLUDE_DIRS
和<PackageName>_LIBRARIES
的值,而且避免硬编码。具体又能够包括:
set()
和list(APPEND <Var>)
来设定变量的值find_library()
来找库文件和头文件(比直接写为固定值要灵活)步骤3:find_package_handle_standard_args(<PackageName> DEFAULT_MSG <PackageName>_INCLUDE_DIRS <PackageName>_LIBRARIES)
步骤4:根据是否查找成功进行打印
这里假设个人package叫作milk,对应的目录结构为:
--- milk --- include --- milk.h --- lib --- milk.lib
Findmilk.cmake
# 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.lib find_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 ()
能够看到,这种case的写法很简单直观。
这个例子中假设依赖项叫作LEMON,目录结构:
lemon --- lib --- debug --- lemon_core.lib --- lemon_extra.lib --- release --- lemon_core.lib --- lemon_extra.lib
debug和release库的处理
但愿在调用find_package(xxx)
以后,Visual Studio或XCode等IDE能自动切换debug和release的库。则须要为debug库的路径添加debug字段,为release库添加optimized字段。
e.g.
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 ()
注意:CMAKE_FIND_LIBRARY_SUFFIXES
的使用
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
ref: https://stackoverflow.com/questions/14243524/cmake-find-library-matching-behavior
场景: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()
缺点:
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})
ref: gcc和ld 中的参数 --whole-archive 和 --no-whole-archive
compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set
设定变量而且设定PARENT_SCOPE
参数。
目录结构举例:
helloworld - CMakeLists.txt - src hello.cpp hello_internal.h CMakeLists.txt - inc hello.h CMakeLists.txt - testbed main.cpp CMakeLists.txt
当项目中代码变多,就可能须要分红多个目录存放。每一个目录下放一个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)
包括两步:
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(TARGET ${prj_target} PROPERTY FOLDER Samples)
效果图:
ref: CMake显式添加文件夹
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
VS2019开始,须要用-A参数指定是32位程序,仍是64位程序。之前的用法不行了。
32位:
cmake -G "Visual Studio 16 2019" -A Win32 ..\..
64位:
cmake -G "Visual Studio 16 2019" -A x64 ..\..