在程序开发的过程中,往往会引入库(动态库和静态库)。有时会只有其中一种,有时可能会二者混用。在这种情况下,就引出了一个问题,如何在工程中对库进行支持(这里不涉及库的版本管理,也就是不处理DLL HELL之类的问题)。这个有很多种方式,不同的平台和不同的开发工具,有着不同的处理手段。比如在VS上和在Idea上都有不同,而不同的语言可能处理也有不同,这些都需要开发者自己根据实际情况来处理。
这里只提在Linux上使用Cmake进行第三方库管理的一些方法。
在Linux使用第三库的方式,如果采用Cmake构建管理的话,主要有以下几种:
1、直接编译编译
这是最常用的一种方式,开发者都用过。老的方式使用方法一般是:
mkdir build
cd build
cmake ..
make -j5
make install
不过高版本的CMake支持了下面的更简单的方式:
cmake -B build -DCMAKE_PREFIX_PATH=/home/fpc/Qt65/6.5.0/gcc_64 (通过 -S 可以 指定编译路径 -G指定构造方式 如Ninja)
cmake --build build -j5(--parallel 5)
B站上的大哥把这个叫做现代CMake,有道理。
这种方式比较适合初学者或者对项目管理简单的地方。优点是容易理解,好操作;缺点是需要手动在项目的CMake文件进行库的编译链接控制和管理。类似于下面:
find_package (库名称 1.6.0 REQUIRED)
add_executable (myapp main.cpp)
target_link_libraries (myapp 库名称)
如果有版本不同的或者不同的机器不同的开发环境,团队间配合时可能出现一些意想不到的问题。这种问题的特点是随机且不容易发现,所以在稍微大一些项目中如果采用这种方式 ,就必须有严格的库版本管理和依赖管理说明。可是大家都明白,靠人,往往是靠不住的。
2、CMake配合git submodules子模块控制
这种方法适用于团队间的源码级的有效管理,不适合只提供库的第三方,而且,第三方库和其它工程为平行关系,即第三库不属于某个工程,一定注意。在合适的路径下使用:
增加子模块:
cd examples
git submodule add http://xxx.xxx/libname.git
下载子模块:
git clone --recursive git@xxxxxx/parent_libname.git
或者没有使用 --recursive 下载完成后,进入子目录执行:
git submodule update --init
然后在工程项目的总CMakeLists.txt文件中增加:
add_subdirectory(${CMAKE_SOURCE_DIR}/examples/libname)
在应用第三方库的项目CMakeLists.txt中增加:
add_dependencies(SUPRA_Lib libname)
TARGET_INCLUDE_DIRECTORIES(SUPRA_Lib
PUBLIC ${SUPRA_Lib_INCLUDEDIRS} libname_include
)
target_link_libraries(SUPRA_Lib ${SUPRA_Lib_LIBRARIES} libname_path)
这种方式可以紧绑定库,不为外界影响。源码级管理,方便控制。缺点也很明显,带着一大堆的外部的库如果升级比较麻烦,同时,每一个使用的项目都得增加上面的依赖项目等。
3、直接加到工程子目录
这和上面的不同在于,这个第三方库真得成为了某个工程的子目录,也就是说add_subdirectory在某个项目的CMakeLists.txt下,比如谷歌的glog就提供了这样的方式:
add_subdirectory(${CMAKE_SOURCE_DIR}/examples/libname)
target_link_libraries(firt libname)
就是这么简单豪放,直接就可以用,不用处理什么头文件还有add_dependencies啊find_package之类的。缺点是可能不如分成模块划分更清晰一些,大工程可能有点让人维护上不方便。
4、使用外部工程的方法
这个的使用详细方法可以参考一下CMake的文档,这里给出一个例子:
include(ExternalProject)
SET(NodeEditor_DIR "${CMAKE_CURRENT_BINARY_DIR}/NodeEditor")
SET(NodeEditor_GIT_REPOSITORY "https://github.com/goeblr/nodeeditor.git" CACHE STRING "")
SET(NodeEditor_GIT_TAG "3bcba3740d68932f3ffaa4b3dc73e624fe51e2db" CACHE STRING "")
ExternalProject_Add(
NodeEditor
PREFIX "${NodeEditor_DIR}"
LOG_DOWNLOAD TRUE
LOG_UPDATE TRUE
LOG_CONFIGURE TRUE
LOG_BUILD TRUE
LOG_INSTALL TRUE
SOURCE_DIR "${NodeEditor_DIR}"
BINARY_DIR "${NodeEditor_DIR}_build"
STAMP_DIR "${NodeEditor_DIR}_stamp"
TMP_DIR "${NodeEditor_DIR}_tmp"
#--Download step--------------
GIT_REPOSITORY ${NodeEditor_GIT_REPOSITORY}
GIT_TAG ${NodeEditor_GIT_TAG}
#--Configure step-------------
CMAKE_ARGS
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}
-DCMAKE_BUILD_TYPE=Release
-DBUILD_EXAMPLES=OFF
-DQt5_DIR=${Qt5_DIR}
-DCMAKE_INSTALL_PREFIX=${NodeEditor_DIR}_install
-DNODE_EDITOR_STATIC=
#--Build step-----------------
#BUILD_ALWAYS 0
#--Install step-----------------
INSTALL_DIR=${NodeEditor_DIR}_install
)
SET(NodeEditor_LIBRARIES "nodes")
INCLUDE_DIRECTORIES(SUPRA_GUI "${NodeEditor_DIR}_install/include")
LINK_DIRECTORIES(SUPRA_GUI "${NodeEditor_DIR}_install/lib")
然后还在当前工程中:
TARGET_LINK_LIBRARIES(SUPRA_GUI
${NodeEditor_LIBRARIES})
add_dependencies(SUPRA_GUI SUPRA_Lib NodeEditor)
这种方式并不简单,但应用灵活,可以说凡是可以用到的代码都可以如此操作。缺点也很明显,复杂啊,而且还要处理很多和编译无关的东西。
CMake目前看应该是C++工程应用中的主流模式了。用它的好处是比写MakeFile要简单多了,更适合于开发者理解,相对来说简单很多。缺点是这玩意儿宏定义太多,各种错误也不好理解,如果遇到一些稀奇古怪的问题,可能老手都得流泪。瑕不掩瑜,勤学多用,主动避开一些坑儿,就会发现CMake还是挺不错的一种工具。