cmake杂记
在sheep_cpp中使用cmake的find_package来提供对外使用,并且第三方库依赖了vcpkg,感觉这种方式还是太重了
因此研究了一下去除了对vcpkg的依赖
最终安装后所有头文件(包括依赖的项目头文件)在安装目录的include下,
静态库全部打包在了一起(包括依赖的项目静态库)在安装目录的lib下,通过-lsheep_cpp -lpthread -ldl
即可链接
在这个过程中遇到很多问题,对一些进阶知识或者常用用法记录一下
主项目常用的目录结构
在根目录下存在一个CMakeLists.txt,存在一个cmake文件夹放一些common的cmake代码
- 源码文件放在src下
- 只存在源码文件
- 测试代码放在test下
- test下有一个CMakeLists.txt,进行测试代码的编译,在根目录的CMakeLists.txt通过
add_subdirectory(test)
来引入他
- test下有一个CMakeLists.txt,进行测试代码的编译,在根目录的CMakeLists.txt通过
- 第三方依赖放在thirdparty下
- 第三方库使用git submodule来完成依赖关系
- 每个第三方库xxx有一个xxx.cmake文件
查找文件或者文件夹
主要依赖GLOB_RECURSE实现,由于cmake的GLOB_RECURSE性能很差
因此最好是通过file(READ...)
和file(WRITE...)
将查找到的结果缓存起来
1 | if (EXISTS ${xxx}) |
下面的全部场景都进行了缓存
要把所有源文件找到进行add_library
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#init Dirs
list(APPEND Dirs ${BasePath}/small_server)
list(APPEND Dirs ${BasePath}/small_client)
list(APPEND Dirs ${BasePath}/net)
list(APPEND Dirs ${BasePath}/log)
list(APPEND Dirs ${BasePath}/extends)
#get all source files
if (EXISTS ${SourceFilesCachePath})
file(READ ${SourceFilesCachePath} SOURCE_FILES)
message(STATUS "read source files from cache ${SourceFilesCachePath}")
else()
foreach(Dir ${Dirs})
file(GLOB_RECURSE SOURCE_FILES_TEMP ${Dir} ${Dir}/*.cpp)
IGNORE_DIR(SOURCE_FILES_TEMP)
list(APPEND SOURCE_FILES ${SOURCE_FILES_TEMP})
endforeach()
file(WRITE ${SourceFilesCachePath} "${SOURCE_FILES}")
message(STATUS "write source files to cache ${SourceFilesCachePath}")
endif()使用GLOB_RECURSE来找到希望遍历的子目录,并且将结果缓存在SourceFilesCachePath文件里面
要把所有头文件找到进行target_include_directories
首先实现了一个查找子目录的函数
默认的GLOB_RECURSE只能使用
LIST_DIRECTORIES true
查找包含子目录的某些文件,因此需要使用IS_DIRECTORY来将子目录筛选出来1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25function(GET_SUB_DIRS result curdir cache_file_prefix)
# 检查缓存文件是否存在
set(cache_file ${cache_file_prefix}/${curdir}.txt)
if(EXISTS ${cache_file})
# 从缓存文件读取子目录
file(READ ${cache_file} subdirs)
message(STATUS "reading from cache file: ${cache_file}")
else()
file(GLOB_RECURSE all_content LIST_DIRECTORIES true ${curdir} ${curdir}/*)
set(subdirs ${curdir})
foreach(item IN LISTS all_content)
if(IS_DIRECTORY ${item})
list(APPEND subdirs ${item})
endif()
endforeach()
# 将子目录写入缓存文件
file(WRITE ${cache_file} "${subdirs}")
message(STATUS "writing to cache file: ${cache_file}")
endif()
set(${result} ${subdirs} PARENT_SCOPE)
#message(STATUS "${curdir} temp dir: ${subdirs}")
endfunction()然后实现了一个排除某些文件夹的函数
1
2
3
4
5
6
7
8
9list(APPEND IgnoreDirs "test")
list(APPEND IgnoreDirs "build")
function(IGNORE_DIR INPUT_SOURCE_FILES_VAR)
set(SOURCE_FILES_TEMP ${${INPUT_SOURCE_FILES_VAR}}) # 获取传入变量的值
foreach(Dir ${IgnoreDirs})
list(FILTER SOURCE_FILES_TEMP EXCLUDE REGEX ${Dir})
endforeach()
set(${INPUT_SOURCE_FILES_VAR} ${SOURCE_FILES_TEMP} PARENT_SCOPE) # 将修改的列表传回父作用域
endfunction()最终使用这两个函数
1
2
3
4
5
6
7
8target_include_directories(${PROJECT_NAME} PRIVATE ${BasePath})
foreach(Dir ${Dirs})
GET_SUB_DIRS(SUBDIRS ${Dir} ${SubDirsCachePath})
IGNORE_DIR(SUBDIRS)
foreach(SUBDIR IN LISTS SUBDIRS)
target_include_directories(${PROJECT_NAME} PRIVATE ${SUBDIR})
endforeach()
endforeach()
最后还需要把所有的头文件按照原本的目录结构install到安装目录去
1
2
3
4
5
6
7
8
9
10
11
12
13
14if (EXISTS ${HeadFilesCachePath})
file(READ ${HeadFilesCachePath} HEADER_FILES)
message(STATUS "read header files from cache ${HeadFilesCachePath}")
else()
file(GLOB_RECURSE HEADER_FILES ${BasePath} ${BasePath}/*.h)
file(WRITE ${HeadFilesCachePath} "${HEADER_FILES}")
message(STATUS "write header files to cache ${HeadFilesCachePath}")
endif()
foreach(HEADER_FILE ${HEADER_FILES})
get_filename_component(FILE_PATH "${HEADER_FILE}" PATH)
string(REPLACE ${BasePath} "include" TARGET_PATH "${FILE_PATH}")
install(FILES "${HEADER_FILE}" DESTINATION "${TARGET_PATH}")
endforeach()在使用GLOB_RECURSE找到所有的头文件以后,为了保持目录结构,需要先用get_filename_component找到绝对路径
然后使用REPLACE替换出相对路径,来进行最终的install
编写依赖的第三方库cmake
在主项目中通过include依赖第三方库的cmake,
随后通过add_dependencies指定依赖的第三方库,如下
1 | include(${CMAKE_SOURCE_DIR}/thirdparty/gflags.cmake) |
随后在thirdparty/glog.cmake
中编写glog_external_project
相关语句,主要就是编译安装第三方库
使用cmake编译的第三方库
1 | include(ExternalProject) #ExternalProject_Add依赖这个系统cmake库 |
使用configure和make编译的第三方库
1 | ExternalProject_Add(libunwind_external_project |
不再给CMAKE_ARGS
填充值
在CONFIGURE_COMMAND
中填充configure
相关语句,在BUILD_COMMAND
和INSTALL_COMMAND
中填充值
如果可以直接make,不需要configure,那么CONFIGURE_COMMAND
需要设置为空,如下
1 | ExternalProject_Add(grpc_external_project |
如果有些headonly的项目完全不需要编译,那么连BUILD_COMMAND
都可以设置为空
1 | ExternalProject_Add(tinytoml_external_project |
复杂的XXX_Command
如果想在XXX_Command写多个命令,那么直接换行Command即可,例如
1 | ExternalProject_Add(grpc_external_project |
如果XXX_Command比较复杂,就需要使用ExternalProject_Add_Step来完成了
例如想在protobuf的cmake的install以后再运行chmod -R +x ${PROTOBUF_INSTALL_DIR}/bin
1 | ExternalProject_Add(protobuf_external_project |
1 | ExternalProject_Add_Step(<external_project_name> <step_name> |
主项目静态库合并依赖的第三方静态库
需求是将一个THIRDPARTY_LIBS
的列表内的静态库合并起来,这个列表是各个第三库相关的cmake中编写的
1 | #in libunwind.a |
听起来很简单,使用find_library
然后COMMAND ar qc
即可
但是最后我发现这些指令都是在运行cmake指令阶段就运行了,而不是在运行make构建指令时运行的
很显然就能想到使用add_custom_command
+add_custom_target
来解决这个问题,但是add_custom_command
的无法运行foreach
等复杂命令,因此只能寻求在add_custom_command
+第三方脚本来解决这个问题
我根据chatgpt的提示,使用add_custom_command
+cmake脚本来解决这个问题,最终失败了
类似于这样
失败方案
1 | set(MERGED_LIB_OUTPUT "merged_lib.a") |
其中merge_libraries.cmake
是一个独立的脚本,作用是将提供的THIRDPARTY_LIBS
全部打包成MERGED_LIB_OUTPUT
内容如下
1 | set(THIRDPARTY_LIBS_PATHS "") |
这个方案坑爹的点,在于${CMAKE_COMMAND} -P "${CMAKE_CURRENT_LIST_DIR}/merge_libraries.cmake"
执行的merge_libraries.cmake
脚本,不带有任何的系统环境变量,因为系统环境变量是通过Project(xxx)
的时候初始化的,通过这种方式编写的脚本,不允许声明Project(xxx)
在不存在系统环境变量的时候find_library
等等指令都是无法运行的
成功方案
最终在Combining several static libraries into one using CMake中找到了可行的办法
1 | function(combine_archives output_archive list_of_input_archives dependencies) |
使用方法
1 | #merge all static libraries |
这里使用首先把需要合并的库写入了一个mri脚本中,随后在add_custom_command
中使用ar -M < ${mri_file}
的方式合并了所有静态库
mri脚本的文件内容大致如下
1 | create libsheep_cpp.a |
但是这里有一个很坑的点,mri脚本作为makefile的扩展,语法支持非常的烂
例如libgrpc++
他无法识别会报错,必须安装的时候改名成libgrpcpp
才能继续使用
然后mri脚本的相关资料又非常的少,因此如果下次遇到这种问题,我大概会直接使用第三方的python脚本来解决问题,毕竟都依赖这么多库了,也不差python了
cmake install的小坑
安装目录文件的时候默认是不会继承文件的属性的,因此在安装二进制文件的时候需要使用USE_SOURCE_PERMISSIONS
显式指定继承
1 | install(DIRECTORY ${GRPC_BIN_DIR} DESTINATION ${CMAKE_INSTALL_PREFIX} USE_SOURCE_PERMISSIONS) |