CMake 使用 install 与 export 导出库
废话不多说,先上成品!
给 VSCode 用的 Snippet 👇
{
"CMake install configure": {
"prefix": "cmake-install",
"body": [
"# --- BEGIN CMAKE INSTALL/EXPORT ---",
"include(GNUInstallDirs)",
"set(PROJECT_EXPORT_TARGETS ${1:\\${PROJECT_NAME\\}})",
"set(PROJECT_EXPORT_NAME ${2:\\${PROJECT_NAME\\}})",
"set(PROJECT_INSTALL_CMAKEDIR ${3:\\${CMAKE_INSTALL_LIBDIR\\}/cmake/\\${PROJECT_EXPORT_NAME\\}})",
"",
"# create aliases for targets",
"foreach(target \\${PROJECT_EXPORT_TARGETS\\})",
"\tadd_library(\\${PROJECT_EXPORT_NAME\\}::\\${target\\} ALIAS \\${target\\})",
"endforeach()",
"# install targets",
"install(",
"\tTARGETS \\${PROJECT_EXPORT_TARGETS\\}",
"\tEXPORT \\${PROJECT_EXPORT_NAME\\}Targets",
"\tARCHIVE DESTINATION \\${CMAKE_INSTALL_LIBDIR\\}",
"\tLIBRARY DESTINATION \\${CMAKE_INSTALL_LIBDIR\\}",
"\tRUNTIME DESTINATION \\${CMAKE_INSTALL_BINDIR\\}",
"\tPUBLIC_HEADER DESTINATION \\${CMAKE_INSTALL_INCLUDEDIR\\}",
"\tINCLUDES DESTINATION \\${CMAKE_INSTALL_INCLUDEDIR\\}",
")",
"",
"# install project headers",
"install(",
"\tDIRECTORY \\${CMAKE_CURRENT_SOURCE_DIR\\}/include/",
"\tDESTINATION \\${CMAKE_INSTALL_INCLUDEDIR\\}",
")",
"",
"# install CMake xxxTargets.cmake",
"export(",
"\tEXPORT \\${PROJECT_EXPORT_NAME\\}Targets",
"\tNAMESPACE \\${PROJECT_EXPORT_NAME\\}::",
"\tFILE \\${PROJECT_EXPORT_NAME\\}Targets.cmake",
")",
"install(",
"\tFILES \\${CMAKE_CURRENT_BINARY_DIR\\}/\\${PROJECT_EXPORT_NAME\\}Targets.cmake",
"\tDESTINATION \\${PROJECT_INSTALL_CMAKEDIR\\}",
")",
"",
"# install xxxConfigVersion.cmake",
"include(CMakePackageConfigHelpers)",
"write_basic_package_version_file(",
"\t\\${PROJECT_EXPORT_NAME\\}ConfigVersion.cmake",
"\tVERSION \\${PROJECT_VERSION\\}",
"\tCOMPATIBILITY AnyNewerVersion",
")",
"install(",
"\tFILES \\${CMAKE_CURRENT_BINARY_DIR\\}/\\${PROJECT_EXPORT_NAME\\}ConfigVersion.cmake",
"\tDESTINATION \\${PROJECT_INSTALL_CMAKEDIR\\}",
")",
"",
"# install CMake xxxConfig.cmake",
"install(",
"\tEXPORT \\${PROJECT_EXPORT_NAME\\}Targets",
"\tFILE \\${PROJECT_EXPORT_NAME\\}Config.cmake",
"\tNAMESPACE \\${PROJECT_EXPORT_NAME\\}::",
"\tDESTINATION \\${PROJECT_INSTALL_CMAKEDIR\\}",
")",
"# --- END CMAKE INSTALL/EXPORT ---"
],
"description": "CMake 导出目标"
}
}
简单讲讲
第一部分
#! 常用的模块了,设置 GNU 风格的 install 目录,变量命名为 CMAKE_INSTALL_xxxDIR
include(GNUInstallDirs)
#! 设置需要导出的目标(列表)
set(PROJECT_EXPORT_TARGETS ${PROJECT_NAME})
#! 设置导出的命名,在这份 snippet 中应用于 xxxConfig.cmake/xxxTargets.cmake/
#! xxxConfigVersion.cmake 和导出的目标的命名空间
set(PROJECT_EXPORT_NAME ${PROJECT_NAME})
#! 设置生成的 cmake 文件的 install 目录,通常会放在顶层目录为 share 或 lib/lib64 的
#! 目录下,在这里选择放在 lib/lib64 目录下
set(PROJECT_INSTALL_CMAKEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_EXPORT_NAME})
第二部分
#! 在同一个 CMake build tree 中,每个项目的 targets 对外都是可见的,比如 A 项目中有一个
#! foobar 的静态库目标,那么在 B 项目中就可以直接链接 foobar 目标。
#! 但是一般情况在 CMake 使用第三方库引入库都会有命名空间(命名冲突问题),这时链接的目标可
#! 能就是 foobar::foobar !(如果不是的话假设就是!!!)
#! 出于统一性考虑,应尽可能使得同一 build tree 内外链接同名目标。
#! 然而 build tree 内即使进行了 install/export 操作,依旧不能直接使用带有命名空间的目标
#! (可能以某种参数进行的 install/export 可以做到,但是我写的时候实在是在这上面找不到办法)
#! 故而曲线救国,在这里额外对导出的目标定义带命名空间的别名,完美!
#! NOTE: 不会吧不会吧?不会有人连这种别名都能造成命名冲突吧?
#! NOTE: 我自认为因为别名没在这里被导出,所以不会影响外部 CMake 项目的引入,或许只不过是我
#> 多虑了,其实根本就不存在这种问题呢?不过说到底我对此机理并未深究,故留个疑问在这。
foreach(target ${PROJECT_EXPORT_TARGETS})
add_library(${PROJECT_EXPORT_NAME}::${target} ALIAS ${target})
endforeach()
第三部分
#! 重头之一:配置待导出目标们的 install 选项,主要包括相关文件的 install 目录,
#! 其中 EXPORT 参数指示了被导出目标集合的导出名,该项将在 export 中被使用以代替显
#! 式地列出目标。另
install(
TARGETS ${PROJECT_EXPORT_TARGETS}
EXPORT ${PROJECT_EXPORT_NAME}Targets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
第四部分
#! 导出头文件
#! 实际上该命令单纯地用于 install 目录(当路径后跟着 "/" 时表示仅导入目录下的文件与子目录)
#! 但是由于我个人的习惯,即项目结构一般服从如下:
#! Project
#! | CMakeLists.txt
#! +---include
#! | \---ProjectInc
#! | [Public Headers]
#! \---src
#! *.cpp
#! [Private Headers]
#! CMakeLists.txt
#! 所以就方便如下这样直接导出公共头文件了!另外在这份 snippet 中还有几个地方也需要前置要求
#! 如上的目录结构,如果要直接挪用的话可能需要注意一下!
install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
第五部分
#! 重头之二:生成用于导入被导出目标的 xxxTargets.cmake 文件
#! export 支持 export(TARGETS) 命令和 export(EXPORT) 命令,在第三部分中提过,install
#! 中指定了待导入目标们的导出名,在此处则直接使用导出名指代被导出的目标。这与代换后的
#! export(TARGETS) 命令是等价的。
export(
EXPORT ${PROJECT_EXPORT_NAME}Targets
NAMESPACE ${PROJECT_EXPORT_NAME}::
FILE ${PROJECT_EXPORT_NAME}Targets.cmake
)
#! export 执行生成的 xxxTargets.cmake 存在于 ${CMAKE_CURRENT_BINARY_DIR} 目录下(也就
#! 是 CMake 构造目录下的与源代码同名子目录),故而需要额外导入该文件。
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_EXPORT_NAME}Targets.cmake
DESTINATION ${PROJECT_INSTALL_CMAKEDIR}
)
第六部分
#! 生成 xxxConfig.cmake 的版本配置脚本 xxxConfigVersion.cmake
#! NOTE: 不是特别重要,可以跳过~
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${PROJECT_EXPORT_NAME}ConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
#! 原因同第五部分,需要手动导入!
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_EXPORT_NAME}ConfigVersion.cmake
DESTINATION ${PROJECT_INSTALL_CMAKEDIR}
)
第七部分
#! 重头之三:生成库的配置文件 xxxConfig.cmake xxxConfig-<config>.cmake
#! 其中 EXPORT 是一个已经被指派的导出名,剩下的部分敢敢当当,一目了然!
install(
EXPORT ${PROJECT_EXPORT_NAME}Targets
FILE ${PROJECT_EXPORT_NAME}Config.cmake
NAMESPACE ${PROJECT_EXPORT_NAME}::
DESTINATION ${PROJECT_INSTALL_CMAKEDIR}
)