記錄使用 cmake 時的常見需求和解決辦法。
- 0. 推薦使用至少3.15版本的cmake
- 1. 用於執行CMake的.bat腳本
- 2. 判斷平台:32位還是64位?
- 3. 判斷Visual Studio版本
- 4. 判斷操作系統
- 5. 判斷是Debug還是Release等版本
- 6. 根據Debug/Release添加不同的庫目錄
- 7. Visual Studio屬性與對應CMake實現方法
- 8. 設定編譯選項
- 9. SAFESEH報錯
- 10. 用了link_directory()但是鏈接不到庫
- 11. Debug庫帶“d”后綴
- 12. 在cmake中執行目錄創建、拷貝文件等腳本
- 13. 現代 CMake
- 13. 創建目錄
- 14. 拷貝文件
- 15. 轉換相對路徑為絕對路徑
- 16. 循環處理列表
- 17. 設置C/C++編譯器
- 18. 設定導入庫(IMPORTED)及其屬性
- 19. 查看並修改Visual Studio項目屬性中的某個設定
- 20. 添加宏定義
- 21. 設置fPIE
- 22. 設置fPIC
- 23. Linux gcc添加鏈接庫"-lm"
- 24. 清空普通變量
- 25. 清除緩存變量
- 26. FindXXX.cmake簡單例子
- 27. CMake各種編譯鏈接參數的默認值
- 28. 鏈接器相關問題
- 29. 生成compile_commands.json
- 30. 子目錄CMakeLists.txt中產生變量給父目錄中的CMakeLists.txt使用
- 31. 在IDE中將targets分組顯示:使用folder
- 32. 設置Debug的優化級別參數
- 33. cmake生成VS2019的工程
- 34. 不要同時使用include_directories()和target_include_directories()
- 35. NDK開發中出現error: cannot use 'try' with exceptions disabled
- 36. cmake-gui中可見(可檢索)的變量
- 37. Ninja error: Filename longer than 260 characters
- 38. cmake判斷C/C++編譯器
- 39. cmake字符串追加元素,並且用空格分隔
- 40. cmake --build的使用:cmake執行編譯鏈接、安裝
- 41. C/C++和匯編混編
- 42. cmake設置pthread
- 43. cmake使用pkg-config
- 44. cmake多行注釋
- 45. 命令行-D指定參數
- 46. include()指令
- 47. list追加元素,或list首部插入元素
- 48. cmake中使用IWYU
- 49. target_link_libraries()
- 50. CMake構建NDK項目提示asm/types.h找不到
- 51. windows下創建的共享庫,沒生成.lib文件
- 52. 拷貝dll
- 53. 壓縮或解壓.zip/tar.gz
- 54. 列出所有target
- 55. 判斷文件是否存在
- 56. 打印變量
- 57. 判斷是否為目錄
- 58. Visual Studio環境下的MT,MTd, MD, MDd的設定
- 59. 設定Address Sanitizer(ASAN)
- 60. Linux下編譯32位程序
- 61. 設置VS的可執行文件生成目錄
- 62. find_package等的debug輸出
- 63. 命令行方式傳入option的坑
- 64. PowerShell中從txt讀內容傳給cmake
0. 推薦使用至少3.15版本的cmake
1. 用於執行CMake的.bat腳本
使用.bat腳本調用cmake,可以指定比較復雜的cmake.exe命令的參數。
e.g. 項目根目錄/build/vs2017-x64.bat
,內容:
@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
2. 判斷平台:32位還是64位?
能寫64位程序的時候輕易別寫32位程序
方法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官方已經明確不推薦使用)
CMAKE_CL_64: Set to a true value when using a Microsoft Visual Studio cl compiler that targets a 64-bit architecture.
適用條件:只適合Windows上的64位判斷。
##################################################
# platform
##################################################
if(CMAKE_CL_64)
message(STATUS "MSVC 64bit")
else()
message(STATUS "MSVC 32bit")
endif()
3. 判斷Visual Studio版本
參考: List of _MSC_VER and _MSC_FULL_VER
##################################################
# 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}")
4. 判斷操作系統
注:其中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中進行了設定。
5. 判斷是Debug還是Release等版本
(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 ()
6. 根據Debug/Release添加不同的庫目錄
在Visual Studio平台下測試發現,如果指定了A目錄到庫搜索目錄,並且A目錄下有名為Debug/Release的目錄,則會自動把A/Debug和A/Release添加到庫搜索目錄。
set(MY_LIB_DIR
"testbed/lib/"
"testbed/lib/ceva/"
)
因而,至少在VS平台下,不需要手動根據Debug和Release來分別添加庫。
7. Visual Studio屬性與對應CMake實現方法
8. 設定編譯選項
也即修改CMAKE_C_FLAGS
、CMAKE_CXX_FLAGS
變量。例如追加兩個選項:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /we4013 /we4431")
9. SAFESEH報錯
報錯信息例如:
CalcSum_.obj : error LNK2026: 模塊對於 SAFESEH 映像是不安全的。 [E:\dbg\zz\assemble\build\vs2013\run.vcxproj]
E:\dbg\zz\assemble\build\vs2013\Debug\run.exe : fatal error LNK1281: 無法生成 SAFESEH 映像。 [E:\dbg\zz\assemble\build\vs2013\
簡單粗暴的解決辦法:
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")
10. 用了link_directory()
但是鏈接不到庫
link_directories() 這句話必須在add_executable()之前寫 不然找不到庫目錄
或者先add_executable() 再 target_link_directories(XXX PRIVATE some direcotry)
11. Debug庫帶“d”后綴
設置debug模式下編譯出的庫文件,相比於release模式下,多帶一個字母"d"作為后綴。
對於單個目錄:
set_target_properties(TargetName PROPERTIES DEBUG_POSTFIX d)
對於整個CMakeLists.txt:
set(CMAKE_DEBUG_POSTFIX d)
也可以在調用cmake時臨時指定(例如google的crc32c庫、libjpeg-turbo庫,作者打死都不肯加postfix的),自己動手豐衣足食:
-DCMAKE_DEBUG_POSTFIX=d
12. 在cmake中執行目錄創建、拷貝文件等腳本
例如創建完調用lmdb庫的可執行程序這一target后,需要創建目錄。不想每次都手動去創建一個目錄,希望在CMakeLists.txt中添加這個操作。
嘗試了add_custom_command()
,不怎么會用。。沒有效果,不被調用。
用execute_process()
則是可以的,例如:
execute_process(COMMAND echo "=====")
13. 現代 CMake
https://moevis.github.io/cheatsheet/2018/09/12/Modern-CMake-筆記.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})
此外,還有一些cmake方面的專家的問答、博客等可以關注一下:
13. 創建目錄
file(MAKE_DIRECTORY ${SO_OUTPUT_PATH})
ref: Creating a directory in CMake
14. 拷貝文件
包括兩種類型:
(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()
15. 轉換相對路徑為絕對路徑
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
16. 循環處理列表
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)
17. 設置C/C++編譯器
例如在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)
注:前一種方法是在單個CMakeLists.txt中設定。對於跨平台編譯,則應當避免污染根CMakeLists.txt,應該為每個平台分別使用cmake cache script。而在cache script中需要設定的變量,都應該是緩存變量。
例如,sigmastar.cmake
:
set(CMAKE_C_COMPILER gcc CACHE STRING "C compiler")
set(CMAKE_CXX_COMPILER g++ CACHE STRING "C++ compiler")
set(PLATFORM_NAME "SigmaStar" CACHE STRING "")
ref: CMake -DCMAKE_CXX_COMPILER works but SET(CMAKE_CXX_COMPILER …) is ignored?
18. 設定導入庫(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開發環境下直接報錯說路徑太長。因此,導入庫並不是一個好的實踐。
19. 查看並修改Visual Studio項目屬性中的某個設定
問題來自StackOverFlow上某網友的提問:Compile error CMAKE with CUDA on Visual Studio C++
解決步驟:
- 用cmake-gui.exe或ccmake加載cmake的cache文件
- 查找需要修改的字符串對應的CMake變量
- 在CMakeLists.txt中修改、覆蓋此變量
20. 添加宏定義
add_definitions(-DUSE_OPENCV)
相當於傳遞給C/C++編譯器:
#define USE_OPENCV
add_definitions(-DLANDMARK_VERSION=2.1.33)
相當於傳遞給C/C++編譯器:
#define LANDMARK_VERSION 2.1.33
21. 設置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")
22. 設置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)
ref:What is the idiomatic way in CMAKE to add the -fPIC compiler option?
23. Linux gcc添加鏈接庫"-lm"
target_link_libraries(xxx m)
24. 清空普通變量
unset(<Var>)
25. 清除緩存變量
unset(<Var> CACHE)
26. 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:想方設法設定<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:根據是否查找成功進行打印
例子1:單個頭文件和單個庫文件
這里假設我的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的寫法很簡單直觀。
例子2:同時存在Debug和Release版本的庫
這個例子中假設依賴項叫做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 ()
例子3:找dll
注意:CMAKE_FIND_LIBRARY_SUFFIXES
的使用
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
ref: https://stackoverflow.com/questions/14243524/cmake-find-library-matching-behavior
實際上,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)
https://stackoverflow.com/a/51126719/2999096
27. CMake各種編譯鏈接參數的默認值
28. 鏈接器相關問題
檢查鏈接到的重名函數
場景: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
,但好處是報錯界面更加友好直觀了:
ref:
gcc和ld 中的參數 --whole-archive 和 --no-whole-archive
lld中的-force_load
參數功能說明是在郵件列表中找到的,官方文檔里還更新出來
ndk-20的change log
29. 生成compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
用於在VSCode等編輯器/IDE中給C/C++代碼做函數定義跳轉支持。
30. 子目錄CMakeLists.txt中產生變量給父目錄中的CMakeLists.txt使用
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)
31. 在IDE中將targets分組顯示:使用folder
包括兩步:
- 在最頂部的CMakeLists.txt中添加一句
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
- 在希望出現在文件夾的項目的add_library或add_executable后添加
set_property(TARGET ${prj_target} PROPERTY FOLDER Samples)
效果圖:
ref: CMake顯式添加文件夾
32. 設置Debug的優化級別參數
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
33. cmake生成VS2019的工程
VS2019開始,需要用-A參數指定是32位程序,還是64位程序。以前的用法不行了。
32位:
cmake -G "Visual Studio 16 2019" -A Win32 ..\..
64位:
cmake -G "Visual Studio 16 2019" -A x64 ..\..
34. 不要同時使用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目錄的方式。
35. 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
即可。
36. cmake-gui中可見(可檢索)的變量
目前遇到的有兩種:
- option( )命令定義的變量,e.g.
option(USE_OPENCV “use opencv?” CACHE PAH)
- 緩存變量。e.g.
set(varName "value" CACHE STRING "")
當然,還可以使用mark_as_adances來設定,則默認在cmake-gui中不可見。
37. 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
38. 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_<LANG>_COMPILER_ID
39. cmake字符串追加元素,並且用空格分隔
簡單有效的兩種方式:
方法1:用set:
set(MY_FLAGS "${MY_FLAGS} -Werror=shadow")
方法2:封裝一個函數:
function(afq_list_append __string __element)
set(${__string} "${${__string}} ${__element}" PARENT_SCOPE)
endfunction()
40. cmake --build
的使用:cmake執行編譯鏈接、安裝
本質上,當使用cmake ..
,或cmake -G "Visual Studio 15 2017 Win64" ../..
類似命令時,是執行“pre-make”,相當於是makefile的生成器,可以說對於你的項目代碼來說並沒執行編譯鏈接,更沒有安裝。
cmake --build的用法說明:(copy自CMake 手冊詳解(二))
--build <dir>: 構建由CMake生成的工程的二進制樹。(這個選項的含義我不是很清楚—譯注)
該選項用以下的選項概括了內置構建工具的命令行界面
<dir> = 待創建的工程二進制路徑。
--target <tgt> = 構建<tgt>,而不是默認目標。
--config <cfg> = 對於多重配置工具,選擇配置<cfg>。
--clean-first = 首先構建目標的clean偽目標,然后再構建。
(如果僅僅要clean掉,使用--target 'clean'選項。)
-- = 向內置工具(native tools)傳遞剩余的選項。
運行不帶選項的cmake --build來獲取快速幫助信息。
實際使用經驗: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
41. C/C++和匯編混編
在cmake里混合編譯C/C++與匯編代碼,通過enable_language(ASM_<DIALECT>)
可以做到。
例如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)
例如src/CalcSum_.cpp
:
.model flat, c
.code
; extern "C" int CalcSum_(int a, int b, int c)
;
; Description: This function demonstrates passing arguments between
; a C++ function and an assembly language function.
;
; Returns: a + b + c
CalcSum_ proc
; Initialize a stack frame pointer
push ebp
mov ebp,esp
; Load the argument values
mov eax,[ebp+8] ; eax = 'a'
mov ecx,[ebp+12] ; ecx = 'b'
mov edx,[ebp+16] ; edx = 'c'
; Calculate the sum
add eax,ecx ; eax = 'a' + 'b'
add eax,edx ; eax = 'a' + 'b' + 'c'
; Restore the caller's stack frame pointer
pop ebp
ret
CalcSum_ endp
end
src/main.cpp
:
#include <stdio.h>
#ifdef USE_ASSEMBLY
extern "C" int CalcSum_(int a, int b, int c);
#else
int CalcSumTest(int a, int b, int c)
{
return a + b + c;
}
#endif
int main() {
int a = 17, b = 11, c = 14;
#ifdef USE_ASSEMBLY
int sum = CalcSum_(a, b, c);
#else
int sum = CalcSumTest(a, b, c);
#endif
printf(" a: %d\n", a);
printf(" b: %d\n", b);
printf(" c: %d\n", c);
printf(" sum: %d\n", sum);
return 0;
}
42. 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)
43. 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}")
再詳細點的話,看這篇:在cmake中使用pkg-config
44. cmake多行注釋
從cmake 3.0版本開始,可以使用多行注釋
#[[
第一種注釋方式
#]]
#[===============[
第二種注釋方式
#]===============]
ref: CMake Multiple line comments - Block Comment
45. 命令行-D
指定參數
在cmake腳本中指定其中任意一種:
- cmake緩存類型的變量,例如
set(CAFFE_TARGET_VERSION "1.0.0" CACHE STRING "Caffe logical version")
- option
option(USE_OPENCV "Do we use OpenCV?" ON)
46. 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
47. list追加元素,或list首部插入元素
追加(鏈表尾部插入):
list(APPEND CMAKE_MODULE_PATH "cmake/Modules")
首部插入(鏈表首元素前插入):
list(INSERT CMAKE_PREFIX_PATH 0 "$ENV{HOME}/soft/opencv/build")
48. cmake中使用IWYU
IWYU 是 google 的開源項目,用來移除不必要的頭文件。在cmake中使用IWYU。
49. target_link_libraries()
cmake 3.13 開始提供的命令。低版本cmake無法使用。
50. 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
來發現問題所在。
51. windows下創建的共享庫,沒生成.lib文件
.lib是導入庫,里面存訪對外可見(暴露)的符號(函數、變量)。.dll應該搭配一個.lib導入庫才能使用。
如果是自己的源碼生成的dll共享庫,則在CMakeLists.txt一開始,添加:
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
則可以導出所有的符號。
ref: CMake linking against shared library on windows: error about not finding .lib file
而如果只想導出一部分符號,則可以為每個函數分別指定導出規則。
52. 拷貝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}/
)
53. 壓縮或解壓.zip/tar.gz
以解壓doctest.zip
到項目根目錄為例:
execute_process(
COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/doctest.zip
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
tar命令是從cmake 3.2開始支持的內置命令。
54. 列出所有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}'
55. 判斷文件是否存在
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()
56. 打印變量
可以用message
命里,例如:
message(STATUS "--- CMAKE_CXX_COMPILER is: ${CMAKE_CXX_COMPILER}")
也可以用如下函數:
include(CMakePrintHelpers)
cmake_print_variables(CMAKE_CXX_COMPILER)
57. 判斷是否為目錄
用IS_DIRECTORY
命令判斷。例如shadow中判斷各個backend,如果是目錄,則添加subdirectory,寫法如下:
foreach (backend_dir ${backends_dir})
if (IS_DIRECTORY ${backend_dir})
add_subdirectory(${backend_dir})
endif()
endforeach()
58. 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.
59. 設定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檢測到。
例如如下代碼中的visit.clear();
導致的后續visit的非法訪問:
/*
請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。
路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。
如果一條路徑經過了矩陣中的某一個格子,則該路徑不能再進入該格子。
a b c e
例如 s f c s
a d e e
矩陣中包含一條字符串"bcced"的路徑,但是矩陣中不包含"abcb"路徑,因為字符串的第一個字符b占據了矩陣中的第一行第二個格子之后,路徑不能再次進入該格子。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Solution {
public:
bool hasPath(char* matrix, int rows, int cols, char* str)
{
bool flag = false;
int len = strlen(str);
int cnt = 0;
vector<vector<char> > mat(rows, vector<char>(cols, '0'));
for(int i=0; i<rows; i++) {
for(int j=0; j<cols; j++) {
mat[i][j] = matrix[cnt];
cnt++;
}
}
vector<vector<int> > visit(rows, vector<int>(cols, 0));
for (int i=0; i<rows; i++) {
for (int j=0; j<cols; j++) {
visit.clear(); //!!!
flag = dfs(0, i, j, rows, cols, len, str, mat, visit);
if(flag) break;
}
}
return flag;
}
private:
bool dfs(int t, int i, int j, int rows, int cols, int len, char* str, const vector<vector<char> >& mat, vector<vector<int> >& visit)
{
if(i<0 || j<0 || i>=rows || j>=cols || visit[i][j]==1) {
return false;
}
if (str[t]!=mat[i][j]) {
return false;
}
visit[i][j] = 1;
if (t==len-1) {
return true;
}
const static int dir[4][2] = {
{-1, 0}, {1, 0},
{0, -1}, {0, 1}
};
bool flag = false;
for (int q=0; q<4; q++) {
flag = dfs(t+1, i+dir[q][0], j+dir[q][1], rows, cols, len, str, mat, visit);
if (flag) break;
}
return flag;
}
};
int main() {
char matrix[] = "abcesfcsadee\0";
int rows = 3;
int cols = 4;
char str[] = "bcced\0";
Solution s;
cout << s.hasPath(matrix, rows, cols, str);
//char str2[] = "abcb";
//cout << s.hasPath(matrix, rows, cols, str2);
return 0;
}
/*
a b c e
s f c s
a d e e
*/
60. Linux下編譯32位程序
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32")
61. 設置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
62. 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信息:
63. 命令行方式傳入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()
64. 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"。
refs: