1. CMake帶來的改變
1.1 依賴關(guān)系的思維轉(zhuǎn)變:用倉庫的概念代替目錄層級(jí)依賴
層級(jí)依賴:
├── TaihuApp
│ └── Qt::Quick
│ └── Qt5::Core
│ └── Qt5::Widgets
│ └── opencv ─────────────────────────────────┐
│ └── logger ─────────────────┐ |
│ └── gtest ────────────────┐ [duplicate] |
│ └── camera | | [duplicate]
│ └── opencv ───|─────|───────────┘
│ └── baumer | |
│ └── tucsen [duplicate] |
│ └── protocol | |
│ └── logger ───|─────┘
│ └── gtest ────────┘
扁平依賴:
├── Repository ────>──>──>──────│
│ └── Qt::Quick |
│ └── Qt5::Core |
│ └── Qt5::Widgets |────────── First Project
│ └── camera |
│ └── logger |
│ └── gtest |────────── Second Project
│ └── opencv |
│ └── baumer |
│ └── tucsen |────────── Other project
│ └── protocol |
│ └── any other libs |
圖一:在每個(gè)項(xiàng)目里都存放一套自身需要的依賴庫灵寺,類似離線式依賴包含關(guān)系曼库;
圖二:camera依賴了opencv、baumer等別的庫略板,但不存在包含關(guān)系毁枯,倉庫里所有庫的依賴關(guān)系都是通過配置進(jìn)行關(guān)聯(lián)的,本質(zhì)所有的庫都在項(xiàng)目之外的倉庫里存放的蚯根。
1.2 簡潔優(yōu)雅的庫依賴集成方式
project(camera VERSION 1.0.0)
find_package(protocol REQUIRED)
find_package(logger REQUIRED)
find_package(timer REQUIRED)
find_package(opencv REQUIRED)
find_package(baumer REQUIRED)
find_package(tucsen REQUIRED)
aux_source_directory(. SRC_LIST)
add_library(${PROJECT_NAME} STATIC ${SRC_LIST})
target_link_libraries(${PROJECT_NAME} PRIVATE
protocol
smt-logger
smt-timer
baumer
tucsen
opencv)
target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
建議用
target_include_directory()
代替include_directory()
后众,如果當(dāng)前也是一個(gè)對(duì)外提供api的庫
1.3 依賴庫版本控制(vcpkg賦能)
{
"name": "project",
"version-string": "1.0.0",
"supports": "(x64 | arm64) & (linux | osx | windows)",
"dependencies": [
{ "name": "zlib", "version>=": "1.2.11#9" },
{ "name": "fmt", "version>=": "7.1.3#1" }
]
}
允許指定當(dāng)前庫的對(duì)外名字、版本颅拦、適用于哪些平臺(tái)系統(tǒng)蒂誉、以及依賴哪些別的庫甚至那些庫的指定版本
2. 自己的庫如何能被find_pakcage(xxx)
cmake有兩種方式讓find_package(xxx)
能找到庫,如果沒有找到會(huì)報(bào)錯(cuò)距帅,如下:
find_package(OpenCV)出現(xiàn)錯(cuò)誤如下:
CMake Warning at CMakeLists.txt:37 (find_package):
By not providing "FindOpenCV.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "OpenCV", but
CMake did not find one.
Could not find a package configuration file provided by "OpenCV" with any of
the following names:
OpenCVConfig.cmake
OpenCV-config.cmake
Add the installation prefix of "OpenCV" to CMAKE_PREFIX_PATH or set "OpenCV_DIR"
to a directory containing one of the above files. If "OpenCV" provides a
separate development package or SDK, be sure it has been installed.
簡單翻譯下:
??cmake優(yōu)先會(huì)以Moudule
模式尋找右锨,即:搜索CMAKE_MODULE_PATH
指定路徑下的FindXXX.cmake
文件,默認(rèn)路徑按系統(tǒng)平臺(tái)區(qū)分如下:
- windows:
C:/Program Files/CMake/share/cmake-3.xx/Modules
- linux:
/usr/share/cmake-3.xx/Modules
??一旦找到了FindXXX.cmake
, 則此庫一般會(huì)提供以下變量碌秸,目的是方便調(diào)用者快速集成它:
<NAME>_FOUND
<NAME>_INCLUDE_DIRS or <NAME>_INCLUDES
<NAME>_LIBRARIES or <NAME>_LIBS
??如果沒能找到FindXXX.cmake
, 則嘗試以Config
模式:搜索指定路徑下的XXXConfig.cmake
或者XXX-config.cmake
文件绍移,搜索路徑優(yōu)先是cmake install的路徑:
- windows:
C:/Program Files
- linux:
/usr/local
當(dāng)然也支持在項(xiàng)目里通過
CMAKE_PREFIX_PATH
指定了尋找路徑,或者直接通過設(shè)置XXX_DIR告知準(zhǔn)確的查找路徑讥电。其實(shí)蹂窖,還有一種做法是通過指定toolchain讓cmake統(tǒng)一從toolchain
里尋找。
2.1 Config方式
??這是一種基于有項(xiàng)目源碼的方式恩敌,需要為cmake組織的項(xiàng)目提供完整的install
腳本瞬测,當(dāng)執(zhí)行install
時(shí)候會(huì)在install
目的地的lib目錄下創(chuàng)建share
目錄,并在share
目錄里自動(dòng)生成XXXConfig.cmake
或者xxx-config.cmake
等配置文件
??cmake install
的腳本相對(duì)比較通用,已經(jīng)被我整理并抽取出來了月趟,一般只要加在cmake項(xiàng)目的實(shí)現(xiàn)模塊的CMakeList.txt
最下面即可灯蝴,如下:
# ============================== install script ==============================
set(HEADERS ${CMAKE_SOURCE_DIR}/include/swc_camera.h)
set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER "${HEADERS}")
# Install the target and create export-set
install(TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}Targets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
PUBLIC_HEADER DESTINATION include)
# Generate the version file for the config file
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
VERSION ${PACKAGE_VERSION}
COMPATIBILITY SameMajorVersion)
# Exporting Targets from the Build Tree
install(EXPORT ${PROJECT_NAME}Targets
DESTINATION "lib/cmake/${PROJECT_NAME}")
# Create config file
configure_package_config_file(
${CMAKE_SOURCE_DIR}/Config.cmake.in ${PROJECT_NAME}Config.cmake
INSTALL_DESTINATION "lib/cmake/${PROJECT_NAME}")
# Install config files
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
DESTINATION "lib/cmake/${PROJECT_NAME}")
可以通過設(shè)置
CMAKE_INSTALL_PATH
指定庫安裝的位置,cmake install library的命令是cmake --build ./ --target install
孝宗, 在linux下配合make可以簡化為make install
穷躁,這是makefile支持的:
cmake --build ./ --target install執(zhí)行后如下:
PS E:\swc-camera\build> cmake --build ./ --target install
Microsoft (R) Build Engine version 16.11.1+3e40a09f8 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.
swc-camera.vcxproj -> E:\swc-camera\build\src\Debug\swc-camera.lib
example.vcxproj -> E:\swc-camera\build\example\Debug\example.exe
tests.vcxproj -> E:\swc-camera\build\tests\Debug\tests.exe
-- Install configuration: "Debug"
-- Up-to-date: E:/LOCAL_REPOSITORY/lib/swc-camera.lib
-- Up-to-date: E:/LOCAL_REPOSITORY/include/swc_camera.h
-- Up-to-date: E:/LOCAL_REPOSITORY/lib/cmake/swc-camera/swc-cameraTargets.cmake
-- Up-to-date: E:/LOCAL_REPOSITORY/lib/cmake/swc-camera/swc-cameraTargets-debug.cmake
-- Up-to-date: E:/LOCAL_REPOSITORY/lib/cmake/swc-camera/swc-cameraConfig.cmake
-- Up-to-date: E:/LOCAL_REPOSITORY/lib/cmake/swc-camera/swc-cameraConfigVersion.cmake
2.2 Module方式
??這是一種當(dāng)?shù)谌綆靸H僅提供了編譯好的binary庫時(shí)候, 有時(shí)候有些庫編譯過程非常復(fù)雜且依賴多而且非常耗時(shí),我們也可以用這種方式因妇,為了讓find_package(xxx)
找到它的方式问潭。我們需要寫一個(gè)對(duì)應(yīng)的FindXXX.cmake
,在FindXXX.cmake
里會(huì)指定嘗試尋找?guī)焖诘穆窂缴尘话惴浅V髁鞯膸靋make的modules目錄會(huì)提供睦授,但以下三種情況需要自己編寫FindXXX.cmake
:
- cmake的modules目錄里提供的
FindXXX.cmake
描述的版本號(hào)和要用的不一致 - 非大眾庫,如baumer或者tucsen摔寨,cmake是不可能提供
FindXXX.cmake
的
在linux/mac系統(tǒng)里去枷,大眾庫的FindXXX.cmake一般存在
/usr/share/cmake-3.xx/Modules
在windows系統(tǒng)里,大眾庫的FindXXX.cmake存在C:\Program Files\CMake\share\cmake-3.xx\Modules
2.2.1 問:自己編寫的FindXXX.cmake放哪里
??答:默認(rèn)find_package(xxx)會(huì)優(yōu)先從cmake的Modules目錄查找是复,意味著我們可以把自己的FindXXX.cmake放到cmake的Modules目錄删顶,但更優(yōu)雅的方式是跟著項(xiàng)目走。在沒有集成vcpkg的情況下淑廊,我們可以在項(xiàng)目根目錄創(chuàng)建一個(gè)cmake目錄逗余,并將各種編寫的FindXXX.cmake
放于此處,隨后需要在項(xiàng)目的CMakeList.txt
里告知FindXXX.cmake
所在目錄季惩,即:list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
录粱, 當(dāng)然有了vcpkg就簡單多了,只要為此庫創(chuàng)建一個(gè)獨(dú)立的倉庫画拾,并將FindXXX.cmake
直接放于其中啥繁,后續(xù)通過vcpkg將其install即可。
2.2.2 問:如何編寫FindXXX.cmake
??答:其實(shí)青抛,FindXXX.cmake
本質(zhì)不一定要寫旗闽,因?yàn)?code>FindXXX.cmake的主要目的是通過find_library
和find_path
指定庫的頭文件和binary所在路徑,但因?yàn)楹芏鄷r(shí)候第三方庫往往有很多頭文件很多庫文件而且還分debug/release蜜另,不能像下面這種方式簡單描述适室,因此有必要提供一個(gè)獨(dú)立的文件來描述庫是怎么尋找和定義的,這樣能讓庫尋找
和庫使用
完全分離解耦举瑰。
find_path(TIFF_INCLUDE_DIR tiff.h
/usr/local/include
/usr/include)
find_library(TIFF_LIBRARY
NAMES tiff tiff2
PATHS /usr/local/lib /usr/lib)
include_directories(${TIFF_INCLUDE_DIRs})
add_executable(mytiff mytiff.c)
target_link_libraries(myprogram ${TIFF_LIBRARY})
因?yàn)閷?shí)際的編寫過程會(huì)很復(fù)雜捣辆,取決于不同形式的庫,因此下面單獨(dú)章節(jié)描述如何編寫FindXXX.cmake.
3. 如何編寫FindXXX.cmake
一個(gè)合格完整的FindXXX.cmake
包含以下3個(gè)部分:
-
定義
XXX_INCLUDE_DIRS
和XXX_LIBRARIES
:find_path()
每次只能獲得一個(gè)頭文件所在路徑此迅,對(duì)于有很多頭文件的庫汽畴,需要通過多次find_path找到各自路徑促煮,并將它們合并為XXX_INCLUDE_DIRS
, 如果一個(gè)庫有很多庫文件,那么也需要多次find_library()找到各個(gè)庫對(duì)應(yīng)的路徑整袁,并將其合并為XXX_LIBRARIES
; -
定義
XXX_FOUND
和XXX_VERSION
: 確認(rèn)XXX_INCLUDE_DIRS
和XXX_LIBRARIES
都不為空佑吝,再定義XXX_FOUND
和XXX_VERSION
坐昙。至此,library已經(jīng)可以被大幅簡化集成芋忿,只是集成時(shí)候需要導(dǎo)入XXX_INCLUDE_DIRS
作為庫頭文件炸客,鏈接XXX_LIBRARIES
作為庫文件,如果庫區(qū)分Debug和Release戈钢,那么cmake還要以optimize和debug方式依賴對(duì)應(yīng)的庫痹仙; -
創(chuàng)建
Target
: 確認(rèn)XXX_FOUND
不為空后再創(chuàng)建Target
,通過add_library()
定義庫類型(SHARED|STATIC|INTERFACE)殉了, 通過set_target_properties()
設(shè)置LIB的頭文件路徑开仰、靜態(tài)庫地址、動(dòng)態(tài)庫地址薪铜、共享庫的地址以及DLL
路徑众弓。至此,庫的集成簡易程度已和源碼庫完全一樣隔箍。
在寫FindXXX.cmake
前需要分析提供的第三方庫的特性谓娃,根據(jù)不同的特性將會(huì)采取不同的方式編寫FindXXX.cmake
:
-
是否單個(gè)頭文件或者單個(gè)庫文件:相對(duì)來說,單個(gè)頭文件和庫文件的庫寫
FindXXX.cmake
會(huì)簡潔很多蜒滩,一個(gè)find_path
和find_library
就能描述所有的依賴關(guān)系滨达; - 庫文件是否區(qū)分debug和release:只有windows庫才有可能區(qū)分debug和release,如果區(qū)分意味著需要讓cmake能動(dòng)態(tài)找到對(duì)應(yīng)版本的庫文件俯艰;
- windows庫除了靜態(tài)庫是否還有動(dòng)態(tài)庫:在定義Target時(shí)候捡遍,需要在property里設(shè)置靜態(tài)庫和動(dòng)態(tài)庫的文件路徑
3.1 單頭文件&單庫文件&單dll的情況
# FindOpenCV
# --------
#
# Find the opencv libraries
#
# Result Variables
# ^^^^^^^^^^^^^^^^
#
# The following variables will be defined:
#
# ``opencv_FOUND`` True if opencv found on the local system
#
# ``opencv_VERSION`` Version of opencv found
#
# ``opencv_INCLUDE_DIRS`` Location of opencv header files
#
# ``opencv_LIBRARIES`` List of the opencv libraries found
#
find_package(PkgConfig)
# ======================= define XXX_ROOT_DIR =======================
if (DEFINED ENV{LOCAL_REPOSITORY})
set(opencv_ROOT_DIR $ENV{LOCAL_REPOSITORY})
endif()
if (DEFINED ENV{VCPKG_ROOT} AND DEFINED ENV{VCPKG_DEFAULT_TRIPLET})
set(opencv_ROOT_DIR $ENV{VCPKG_ROOT}/installed/$ENV{VCPKG_DEFAULT_TRIPLET})
endif()
# ======================= find header files =======================
find_path(opencv_INCLUDE_DIR
NAMES opencv2/opencv.hpp
PATHS ${opencv_ROOT_DIR}/include /usr/local/include)
# ======================= find library files =======================
# define macro func to find libs
macro(opencv_FIND_LIBRARY libname)
if(NOT opencv_${libname}_LIBRARY)
find_library(opencv_${libname}_LIBRARY
NAMES ${libname}
PATHS ${opencv_ROOT_DIR}/lib /usr/local/lib)
list(APPEND opencv_LIBRARY ${opencv_${libname}_LIBRARY})
endif()
endmacro(opencv_FIND_LIBRARY)
if(WIN32)
find_library(opencv_LIBRARY_DEBUG
NAMES opencv_world412d.lib
PATHS ${opencv_ROOT_DIR}/debug/lib /usr/local/lib)
find_library(opencv_LIBRARY_RELEASE
NAMES opencv_world412.lib
PATHS ${opencv_ROOT_DIR}/lib /usr/local/lib)
include(SelectLibraryConfigurations)
select_library_configurations(opencv)
elseif(UNIX)
# call macro func to find libs
opencv_FIND_LIBRARY(libopencv_core.so)
opencv_FIND_LIBRARY(libopencv_cudaarithm.so)
opencv_FIND_LIBRARY(libopencv_cudafilters.so)
opencv_FIND_LIBRARY(libopencv_cudaimgproc.so)
opencv_FIND_LIBRARY(libopencv_highgui.so)
opencv_FIND_LIBRARY(libopencv_imgcodecs.so)
opencv_FIND_LIBRARY(libopencv_imgproc.so)
endif()
# ======================= find bin files =======================
if(WIN32)
find_file(opencv_LIBRARY_DLL_DEBUG
NAMES opencv_world412d.dll
PATHS ${opencv_ROOT_DIR}/debug/bin)
find_file(opencv_LIBRARY_DLL_RELEASE
NAMES opencv_world412.dll
PATHS ${opencv_ROOT_DIR}/bin)
endif()
# ======================= verify dependencies =======================
if (opencv_INCLUDE_DIR AND opencv_LIBRARY)
set(opencv_FOUND TRUR CACHE BOOL "")
set(opencv_VERSION "4.1.2" CACHE STRING "")
set(opencv_INCLUDE_DIRS ${opencv_INCLUDE_DIR} CACHE STRING "")
set(opencv_LIBRARIES ${opencv_LIBRARY} CACHE STRING "")
find_package_handle_standard_args(opencv
REQUIRED_VARS opencv_INCLUDE_DIRS opencv_LIBRARIES
VERSION_VAR opencv_VERSION)
mark_as_advanced(opencv_INCLUDE_DIRS opencv_LIBRARIES)
endif()
# ======================= create target =======================
if (opencv_FOUND)
include(CMakePushCheckState)
cmake_push_check_state()
# set required properties
set(CMAKE_REQUIRED_QUIET ${opencv_FIND_QUIETLY})
set(CMAKE_REQUIRED_INCLUDES ${opencv_INCLUDE_DIRS})
set(CMAKE_REQUIRED_LIBRARIES ${opencv_LIBRARIES})
cmake_pop_check_state()
if(NOT TARGET opencv)
add_library(opencv SHARED IMPORTED)
set_target_properties(opencv PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${opencv_INCLUDE_DIRS}")
if(opencv_LIBRARY_DEBUG)
set_property(TARGET opencv APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(opencv PROPERTIES
IMPORTED_LOCATION_DEBUG "${opencv_LIBRARY_DLL_DEBUG}"
IMPORTED_IMPLIB_DEBUG "${opencv_LIBRARY_DEBUG}")
endif()
if(opencv_LIBRARY_RELEASE)
set_property(TARGET opencv APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(opencv PROPERTIES
IMPORTED_LOCATION_RELEASE "${opencv_LIBRARY_DLL_RELEASE}"
IMPORTED_IMPLIB_RELEASE "${opencv_LIBRARY_RELEASE}")
endif()
if(NOT opencv_LIBRARY_RELEASE AND NOT opencv_LIBRARY_DEBUG)
set_property(TARGET opencv APPEND PROPERTY IMPORTED_LOCATION "${opencv_LIBRARY}")
endif()
endif()
endif()
備注1:
${opencv_ROOT_DIR}
指向的庫目錄是動(dòng)態(tài)的,如果定義了VCPKG_ROOT
, 那么vcpkg就是庫的尋找源蟆炊;如果未定義VCPKG_ROOT
但定義了LOCAL_REPOSITORY
, 那么本地目錄即為庫尋找源稽莉;若都沒有定義,那么頭文件和庫文件就只能從系統(tǒng)路徑尋找了。不管當(dāng)前是哪個(gè)平臺(tái)掰邢,如:x64-windows荷辕、x86-windows、arm64-linux良拼、x64-linux等等,${opencv_ROOT_DIR}
下一般目錄結(jié)構(gòu)都是:include
充边、lib
以及bin
庸推。
3.1 批量尋找頭文件: 模板化find_path()
因?yàn)?code>find_path每次只能尋找一個(gè)頭文件常侦,需要多次調(diào)用將最終結(jié)果合并為XXX_INCLUDE_DIRS
。其實(shí)贬媒,也可以如下通過定義宏或者函數(shù)批量尋找頭文件:
# ======================= find header files =======================
# define macro func to find headers
macro(baumer_FIND_INCLUDE varname foldername headername)
if(NOT baumer_${foldername}_INCLUDE_DIR)
find_path(baumer_${foldername}_INCLUDE_DIR
NAMES ${foldername}/${headername}
PATHS ${baumer_ROOT_DIR}/include /usr/local/include)
list(APPEND baumer_INCLUDE_DIRS ${baumer_${foldername}_INCLUDE_DIR})
list(REMOVE_DUPLICATES baumer_INCLUDE_DIRS)
endif()
endmacro(baumer_FIND_INCLUDE)
# call macro func to find headers
baumer_FIND_INCLUDE(bgapi2_ext bgapi2_ext bgapi2_ext.h)
baumer_FIND_INCLUDE(bgapi2_ext_addons bgapi2_ext bgapi2_ext_addons.h)
baumer_FIND_INCLUDE(bgapi2_ext_sc bgapi2_ext_sc bgapi2_ext_sc.h)
baumer_FIND_INCLUDE(bgapi2_def bgapi2_genicam bgapi2_def.h)
baumer_FIND_INCLUDE(bgapi2_featurenames bgapi2_genicam bgapi2_featurenames.h)
baumer_FIND_INCLUDE(bgapi2_genicam bgapi2_genicam bgapi2_genicam.hpp)
最終聋亡,baumer_INCLUDE_DIRS
=E:\vcpkg\installed\x64-windows\include
,你可能會(huì)疑惑那么多次的find_path
定位到的include路徑都是一樣的际乘,不是浪費(fèi)么坡倔。No,其實(shí)目的是確保每個(gè)頭文件都能被find_path
到脖含。
以上腳本演示了將每個(gè)public的頭文件被尋找到并成為
baumer_INCLUDES
的一部分罪塔,庫調(diào)用者include頭文件則為#include <foldername/headername>
,如:#include <bgapi2_ext/bgapi2_ext.h>
3.2 批量尋找?guī)煳募耗0寤痜ind_library()
和find_path
一樣find_library
每次只能尋找一個(gè)庫文件养葵,需要多次調(diào)用將最終結(jié)果合并為XXX_LIBRARIES
征堪。同樣也可以如下通過定義宏或者函數(shù)批量尋找?guī)煳募?/p>
# ---------------- find library files----------------
# define macro func to find libs
macro(baumer_FIND_LIBRARY libname)
if(NOT baumer_${libname}_LIBRARY)
find_library(baumer_${libname}_LIBRARY
NAMES ${libname}
PATHS ${baumer_ROOT_DIR}/lib /usr/local/lib)
list(APPEND baumer_LIBRARIES ${baumer_${libname}_LIBRARY})
endif()
endmacro(baumer_FIND_LIBRARY)
# call macro func to find libs
Baumer_FIND_LIBRARY(bgapi2_ext.lib)
Baumer_FIND_LIBRARY(bgapi2_ext_sc.lib)
Baumer_FIND_LIBRARY(bgapi2_genicam.lib)
最終,baumer_LIBRARIES
=E:\vcpkg\installed\x64-windows\lib\bgapi2_ext.libE:\vcpkg\installed\x64-windows\lib\bgapi2_ext_sc.libE:\vcpkg\installed\x64-windows\lib\bgapi2_genicam.lib
3.3 尋找?guī)煳募鼐埽珔^(qū)分DEBUG和RELEASE
對(duì)于一些區(qū)分Debug和Release的windows庫佃蚜,我們不能一味用xxx_LIBRARIES描述所有庫文件:
find_library(serial_LIBRARY_DEBUG
NAMES seriald.lib
PATHS ${serial_ROOT_DIR}/debug/lib /usr/local/lib)
find_library(serial_LIBRARY_RELEASE
NAMES serial.lib
PATHS ${serial_ROOT_DIR}/lib /usr/local/lib)
include(SelectLibraryConfigurations)
select_library_configurations(serial)
select_library_configurations(xxx)的引入使得cmake configure會(huì)自動(dòng)選取對(duì)應(yīng)版本的庫賦值與xxx_LIBRARY, 意味著target_link_library時(shí)候不用區(qū)分optimize和debug的庫
至此着绊,庫的集成將大幅簡化爽锥,因?yàn)榭梢宰龅搅似帘尾煌窂降念^文件不同路徑,不同名字的庫文件畔柔,甚至不用區(qū)分系統(tǒng)平臺(tái):
find_package(XXX)
target_include_directory(app ${XXX_INCLUDE_DIRS})
target_link_library(app PRIVATE
optimize ${XXX_LIBRARIES_RELEASE}
debug ${XXX_LIBRARIES_DEBUG})
3.3 創(chuàng)建Target
為了徹底氯夷、完全做到跟源碼庫集成一樣簡潔:省略頭文件導(dǎo)入和不區(qū)分debug/release,我們可以手動(dòng)創(chuàng)建Target靶擦。
3.3.1 只有單個(gè)static或者單個(gè)so的Target
if(NOT TARGET xxx)
add_library(xxx SHARED|STATIC|UNKNOWN IMPORTED)
set_target_properties(xxx PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${xxx_INCLUDE_DIRS}"
IMPORTED_LOCATION "${xxx_LIBRARY}")
endif()
add_library()時(shí)候可以指定library是
SHARED
庫腮考、STATIC
庫,如果不確定那種也可以填UNKNOWN
玄捕,因?yàn)?code>SHARED庫和STATIC
庫的必要設(shè)置的property是一樣的
3.3.2 只有單個(gè)dll和單個(gè)lib的Target
if(NOT TARGET xxx)
add_library(xxx SHARED IMPORTED)
set_target_properties(xxx PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${xxx_INCLUDE_DIRS}"
IMPORTED_LOCATION "${xxx_LIBRARY_DLL}"
IMPORTED_IMPLIB "${xxx_LIBRARY}")
endif()
3.3.3 有多個(gè)static和多個(gè)dll的Target
if(NOT TARGET xxx)
add_library(xxx INTERFACE IMPORTED)
set_target_properties(xxx PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${xxx_INCLUDE_DIRS}"
INTERFACE_LINK_LIBRARIES "${xxx_LIBRARIES}"
IMPORTED_LOCATION "${xxx_LIBRARY_DLLS}")
endif()
3.3.4 區(qū)分Debug/Release的Target
if(NOT TARGET xxx)
add_library(xxx STATIC IMPORTED)
set_target_properties(xxx PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${xxx_INCLUDE_DIRS}")
if(xxx_LIBRARY_DEBUG)
set_property(TARGET xxx APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(xxx PROPERTIES IMPORTED_LOCATION_DEBUG "${xxx_LIBRARY_DEBUG}")
endif()
if(xxx_LIBRARY_RELEASE)
set_property(TARGET xxx APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(xxx PROPERTIES IMPORTED_LOCATION_RELEASE "${xxx_LIBRARY_RELEASE}")
endif()
if(NOT xxx_LIBRARY_RELEASE AND NOT xxx_LIBRARY_DEBUG)
set_property(TARGET xxx APPEND PROPERTY IMPORTED_LOCATION "${xxx_LIBRARY}")
endif()
endif()
這是一個(gè)典型的兼容Windows和Linux的Target創(chuàng)建案例踩蔚,因?yàn)楹芏鄷r(shí)候Windows庫區(qū)分Debug和Release,而Linux的SO庫是不區(qū)分的枚粘。
至此馅闽,庫的集成將變?yōu)槿缦路绞剑?/p>
find_package(xxx REQUIRED)
target_link_library(app xxx)
4. vcpkg
??CMake提供了依賴庫尋找的功能,當(dāng)找不到依賴庫時(shí)只會(huì)報(bào)錯(cuò)馍迄,并不會(huì)自動(dòng)下載依賴庫福也,因?yàn)镃Make不是真正的包管理器,頂多是一個(gè)包尋找器攀圈,所以得配合vcpkg暴凑,vcpkg是宗旨是即時(shí)編譯當(dāng)前或者指定平臺(tái)的庫,vcpkg內(nèi)部提供了眾多下載依賴途徑選項(xiàng)赘来,如:http, ftp, gitlab, github, bitbucket现喳,svn凯傲,cvs等等。
4.1 如何讓vcpkg和cmake結(jié)合讓庫依賴自動(dòng)化
??vcpkg的詳細(xì)功能豐富嗦篱,可自行查看官網(wǎng)冰单,這里只指導(dǎo)如何快速上手創(chuàng)建私有port,并讓vcpkg和cmake集成達(dá)到編譯即下載依賴庫的作用灸促,我們分別以2種案例來介紹:
- 基于源碼構(gòu)建的庫:相對(duì)更簡單
- 基于只有頭文件和二進(jìn)制庫文件的第三方庫: 需要區(qū)分多平臺(tái)獨(dú)立球凰,復(fù)雜一些
4.2 創(chuàng)建私有port
??所謂port就是一個(gè)以庫名為名字的目錄,里面放此庫的獲取來源配置
腿宰、版本及依賴關(guān)系配置
、使用說明
等缘厢,創(chuàng)建一個(gè)私有port的步驟如下:
- 在你的C++項(xiàng)目根目錄創(chuàng)建一個(gè)文件夾叫
vcpkg-ports
, 如果你的庫名叫xxx吃度,那么在vcpkg-ports
里創(chuàng)建一個(gè)目錄叫xxx,那么這個(gè)xxx即是一個(gè)port贴硫。 - 在port目錄里創(chuàng)建一個(gè)名為
portfile.cmake
的文本文件椿每,在此配置文件里定義庫代碼或者二進(jìn)制獲取方式,以及相關(guān)配置英遭。
portfile.cmake (以源碼方式將vcpkg和cmake集成):
set(SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src/${PORT})
vcpkg_from_git(
OUT_SOURCE_PATH SOURCE_PATH
URL ssh://git@lmsman-bitbucket01.europe.leicams.com:7999/lmswet-bitbucket01/~fei.zhang/smt-timer.git
REF 9e950a8a52b7304e7da2ab59fd485f39095dca9b
HEAD_REF master
)
# configure project and try to enable ninja
vcpkg_configure_cmake(
SOURCE_PATH ${SOURCE_PATH}
PREFER_NINJA)
vcpkg_install_cmake()
vcpkg_copy_pdbs()
# relocate target to vcpkg
vcpkg_fixup_cmake_targets(
CONFIG_PATH lib/cmake/${PORT}
TARGET_PATH /share/${PORT})
# remove headers in debug mode
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)
# install license and copyright
file(
INSTALL ${SOURCE_PATH}/COPYING
DESTINATION ${CURRENT_PACKAGES_DIR}/share/${PORT}
RENAME copyright)
用已經(jīng)編譯好的二進(jìn)制庫的方式將vcpkg和cmake集成:
set(SOURCE_PATH "${CURRENT_BUILDTREES_DIR}/src/${PORT}")
vcpkg_from_git(
OUT_SOURCE_PATH SOURCE_PATH
URL git@10.10.231.59:fei.zhang/baumer.git
REF 2d2d0a217da810f1ea71ca807c5337aed3e84e12
HEAD_REF master
)
if (WIN32)
# install headers
file(GLOB_RECURSE HEADERS "${SOURCE_PATH}/windows/include/*")
foreach(HEADER ${HEADERS})
get_filename_component(ABSOLUTE_DIR ${HEADER} DIRECTORY)
string(REGEX MATCH "include.+" NODE_DIR ${ABSOLUTE_DIR})
file(INSTALL ${HEADER} DESTINATION "${CURRENT_PACKAGES_DIR}/${NODE_DIR}")
endforeach()
# install libs
file(GLOB_RECURSE LIBRARIES "${SOURCE_PATH}/windows/lib/*")
file(INSTALL ${LIBRARIES} DESTINATION "${CURRENT_PACKAGES_DIR}/lib")
# install dlls
file(GLOB_RECURSE BINS "${SOURCE_PATH}/windows/bin/*")
file(INSTALL ${BINS} DESTINATION "${CURRENT_PACKAGES_DIR}/bin/${PORT}")
elseif (UNIX)
# install headers
file(GLOB_RECURSE HEADERS "${SOURCE_PATH}/linux/include/*")
foreach(HEADER ${HEADERS})
get_filename_component(ABSOLUTE_DIR ${HEADER} DIRECTORY )
string(REGEX MATCH "include.+" NODE_DIR ${ABSOLUTE_DIR})
file(INSTALL ${HEADER} DESTINATION "${CURRENT_PACKAGES_DIR}/${NODE_DIR}")
endforeach()
# install libs
file(GLOB_RECURSE LIBRARIES "${SOURCE_PATH}/linux/lib/*")
file(INSTALL ${LIBRARIES} DESTINATION "${CURRENT_PACKAGES_DIR}/lib")
endif()
# install FindXXX.cmake allow App can find pakcage via find_package(xxx)
file(INSTALL ${SOURCE_PATH}/FindBaumer.cmake DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
# relocate the directory for cmake to find FindXXX.cmake
file(INSTALL ${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
# install cmake integration usage
file(INSTALL ${CMAKE_CURRENT_LIST_DIR}/usage DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
# allow incomplete packages to pass validation
set(VCPKG_POLICY_EMPTY_PACKAGE enabled)
- 在此port目錄里創(chuàng)建一個(gè)名為
vcpkg.json
的文本文件间护,在此配置里定義當(dāng)前庫的基礎(chǔ)信息以及依賴關(guān)系等,如下以swc-camera
的vcpkg.json
為例(如果此庫沒有其他任何依賴且沒有版本挖诸,可以不用提供):
{
"name": "swc-camera",
"version-string": "v1.0.0",
"description": "swc-camera is an integrated camera feature sdk",
"dependencies": [
"protocol",
"smt-logger",
"smt-timer",
"opencv",
"baumer",
"tucsen",
"gtest"
]
}
4.3 如何通過vcpkg自動(dòng)下載庫和依賴庫
以swc-camera
為例汁尺,安裝命令為: vcpkg install swc-camera --overlay-ports=vcpkg-ports
當(dāng)看到如下過程log,意味著下載swc-camera
以及其依賴庫已經(jīng)開始工作了
--port-overlay=vcpkg-ports
作用是指定vcpkg安裝庫多律,庫的port配置來自vcpkg-ports
目錄痴突,如果項(xiàng)目不依賴任何私有托管的倉庫,則不用指定--port-overlay
PS E:\vcpkg> .\vcpkg.exe install swc-camera --overlay-ports=vcpkg-ports
Computing installation plan...
The following packages will be built and installed:
* baumer[core]:x64-windows -> v1.0.0 -- E:\vcpkg\vcpkg-ports\baumer
* opencv[core]:x64-windows -> v4.1.2 -- E:\vcpkg\vcpkg-ports\opencv
* smt-logger[core]:x64-windows -> v1.0.0 -- E:\vcpkg\vcpkg-ports\smt-logger
* smt-timer[core]:x64-windows -> v1.0.0 -- E:\vcpkg\vcpkg-ports\smt-timer
swc-camera[core]:x64-windows -> v1.0.0 -- E:\vcpkg\vcpkg-ports\swc-camera
* tucsen[core]:x64-windows -> v1.0.0 -- E:\vcpkg\vcpkg-ports\tucsen
Additional packages (*) will be modified to complete this operation.
Detecting compiler hash for triplet x64-windows...
當(dāng)最后出現(xiàn)如下信息狼荞,意味著swc-camera
安裝成功:
Total elapsed time: 16.2 s
The package swc-camera:x64-windows provides CMake targets:
find_package(swc-camera CONFIG REQUIRED)
target_link_libraries(main PRIVATE swc-camera)
4.4 如何通過vcpkg卸載庫
以swc-camera
為例辽装,卸載命令為: vcpkg remove swc-camera --port-overlay=vcpkg-ports
需要注意的是remove不會(huì)將依賴庫一同卸載,因?yàn)閏make庫依賴都是引用依賴相味,不是包含依賴拾积,你不用的庫可能別的項(xiàng)目在用。
4.5 如何將vcpkg集成到cmake項(xiàng)目中
# use local repository if defined
if (DEFINED ENV{LOCAL_REPOSITORY})
set(CMAKE_PREFIX_PATH $ENV{LOCAL_REPOSITORY})
endif()
# preferred to use vcpkg if defined
if(DEFINED ENV{VCPKG_ROOT})
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake")
endif()
---------------------------------------------------------------------
project(testApp)
find_package(smt-logger)
add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} smt-logger)
如上丰涉,演示了如何集成vcpkg到cmake項(xiàng)目拓巧,同時(shí)也指定了本地統(tǒng)一庫尋找目錄,且有尋找優(yōu)先級(jí)一死。
- 首先玲销,find_package()默認(rèn)優(yōu)先會(huì)嘗試從vcpkg里尋找,假設(shè)系統(tǒng)環(huán)境變量定義了vcpkg根目錄 ——
VCPKG_ROOT
- 隨后摘符,若
VCPKG_ROOT
未定義(假設(shè)你不喜歡vcpkg贤斜,想自己折騰)策吠,則嘗試從本地統(tǒng)一庫尋找目錄里尋找,假設(shè)系統(tǒng)環(huán)境變量定義了統(tǒng)一庫尋找目錄 ——LOCAL_REPOSITORY
- 如果以上環(huán)境變量都沒有瘩绒,那么則嘗試找默認(rèn)的路徑猴抹,linux從
/usr/local
里找,windows從C:/Program File
里找