目錄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 ..\..