你是否有过在正式写代码之前焦头烂额地编写一大堆CMake构建代码?你是否有过在封装自己的库时写了一堆install()、export()等CMake安装导出代码?你是否曾希望过自己能快速构建出能够被别人find_package得到的库?你是否曾想过自己能够快速构建出像boost那样的多组件库?那么,接下来的内容,将会让你眼前一亮,耳目一新。
Github链接
简化项目安装过程中的大量代码,使用单个函数封装一些导出和安装操作,以方便构建安装单个项目或多组件项目。
PKG(
_NAME PKG_lib
_INCLUDE_DIRS "include/" # 此处意为“include中的所有文件或目录将被安装,但不包括include本身”
)
执行库安装后,就可以在其他项目中引用PKG_lib
库:
find_package(PKG_lib REQUIRED)
怎么样,是不是很简单?
那么我们再来看看怎么构建安装可执行文件(即不要include和lib)↓
PKG(
_NAME PKG_exe
_MODE Runtime
_INSTALL_BIN_DIR "." # Windows系统适用:默认可执行文件安装在安装目录的bin目录,该代码可以使可执行文件安装到安装目录根目录上
_DISABLE_INTERFACE
_DISABLE_CONFIG
_DISABLE_VERSION
)
进行编译安装(不懂怎么使用cmake进行库编译安装的自己去百度,本篇文章不属于基础教学),你就会神奇的发现,它安装到了C:\Program Files (x86)\PKG_exe
目录中,当然这个安装目录是可以修改的,以上例子只是简单的介绍基本功能。
cmake_minimum_required(VERSION 3.20)
project(PKG_shared VERSION 1.2.3.4)
add_library(PKG_shared SHARED)
add_library(PKGNS::PKG_shared ALIAS PKG_shared)
set_target_properties(
PKG_shared PROPERTIES
VERSION 1.2.3.4
#SOVERSION 1.2
DEFINE_SYMBOL "${COMPONENT_NAME}_EXPORTS"
MSVC_RUNTIME_LIBRARY "${MSVC_RUNTIME_LIB}"
)
include(cmake/PKG.cmake)
PKG(
_NAME "PKG_shared"
#_VERSION 1.2.3.4 # 因为PKG_shared已经定义了VERSION属性,这句就免了
_DEBUG_POSTFIX "d" # 相当于‘set_target_properties(PKG_shared PROPERTIES DEBUG_POSTFIX "d")’
_NAMESPACE "PKGNS"
_DEPENDENCIES "Soci" "[email protected]:python,thread,system"
_EXPORT_EXT_DIRS_1 "resources" "."
_INSTALL_EXT_DIRS_1 "doc/" "doc"
_INCLUDE_DIRS "include/"
_INCLUDE_EXCLUDE_REG ".*\\.(svn|h\\.in|hpp\\.in)$"
_INCLUDE_DESTINATION "include/${PROJECT_NAME}-1.2.3.4"
_EXPORT_HEADER "comm1_export.h" # 相对于CMAKE_CURRENT_BINARY_DIR
_INSTALL_PDB # 仅MSVC有效
#_ADD_LIB_SUFFIX # 如果在64位计算机上,_BINARY_LIB_DIR和_INSTALL_LIB_DIR的值将添加后缀'64'
_ADD_UNINSTALL
)
target_sources(
PKG_shared PRIVATE "src/add.cpp"
)
target_include_directories(
PKG_shared PRIVATE
"include"
"${PKG_PKG_shared_EXPORT_HEADER_DIR}"
)
如果要打包多组件项目,此脚本将为您提供极大的便利。该功能提供两个特殊选项: _IS_COMPONENT
和_IS_COMPONENTS
。_IS_COMPONENT
指定当前_NAME
目标是项目的一个组件,则此组件将附属于_PROJECT
项目,_PROJECT
在定义_IS_COMPONENT
参数时就默认为PROJECT_NAME
。_PROJECT
可以定制。
主项目部分:
project(PKG VERSION 0.0.1)
# Its components also set this value to the default value of their _INSTALL_DIR, unless custom _INSTALL_DIR
set(PKG_PKG_INSTALL_DIR "/home/pkg/pkg_install")
add_subdirectory(component)
# Called after all child components
PKG(
_IS_COMPONENTS
_NAME ${PROJECT_NAME}
_VERSION 0.0.1
#_INCLUDE_DIRS "macro" # 在这里,您可以将其他include目录添加到 _INCLUDE_DESTINATION 指定的路径中
_INCLUDE_FILES "macro/global.h" "macro/macro.h" # 结果同上,只是这里是针对文件而不是目录
_INCLUDE_EXCLUDE_REG ".*\\.(svn|h\\.in|hpp\\.in)$"
#_INCLUDE_DESTINATION "include" # _INCLUDE_DESTINATION 的值默认为 _INSTALL_INCLUDE_DIR,也就是 "include"
_SHARED_LIBS
_ADD_UNINSTALL
)
组件部分:
add_library(component SHARED)
PKG(
_IS_COMPONENT
_NAME component
_PROJECT PKG
_NAMESPACE "${PROJECT_NAME}"
_DEBUG_POSTFIX "d"
_INCLUDE_DIRS "include/"
_INCLUDE_EXCLUDE_REG ".*\\.(svn|h\\.in|hpp\\.in)$"
_INCLUDE_DESTINATION "include/${PROJECT_NAME}"
_EXPORT_HEADER "include/component_export.h" # 将定义 PKG_PKG_component_EXPORT_HEADER_DIR 变量
_INSTALL_PDB
)
接下来,您可以在其他项目中使用find_package()
来查询该组件:
find_package(PKG COMPONENTS component REQUIRED)
...
target_link_libraries(... PRIVATE PKG::component)
...
PKG()的功能很强大,详细介绍可前往Github查看,目前Github上已经添加了中文说明。
以下的内容是PKG()的开头中文注释:
#===============================================================================
#
# @brief 快速打包普通项目或多组件项目
#
# @par 可用关键字
# _IS_COMPONENT opt,当前安装的是项目的小部件
# _IS_COMPONENTS opt,当前安装的是组件集,组件集不能是运行时工件,也就是说`_NAME`不能是`add_library`或`add_executable`等命令生成的目标
# _NAME one,项目名称/组件名称(无默认)
# _PROJECT one,`_IS_COMPONENT`开启时可用,指定本组件所附属的项目名称(默认:${PROJECT_NAME})
# _VERSION one,版本(默认:目标(_NAME)属性 VERSION 的值 | 未定义)
# _COMPATIBILITY one,定义目标的版本兼容性,
# 支持的值: `AnyNewerVersion|SameMajorVersion|SameMinorVersion|ExactVersion`(默认:AnyNewerVersion)
# _DEBUG_POSTFIX one,在Debug的编译文件的文件名后面添加标识,例如:"d",对Release无效(无默认)
# _SHARED_LIBS opt,指定函数作用域内的 BUILD_SHARED_LIBS 变量值,将会在PKG_components-config.cmake.in用到
# _DEPENDENCIES mul, 目标的依赖,可以设置多个,将使用`CMakeFindDependencyMacro`模块的find_dependency函数进行查找。
# 内容格式——依赖的库的文件“-config.cmake”或“Config.cmake”的前缀 + “@version”,一般这个前缀就是对应的项目的名称;
# 对于多组件项目,则可以使用“project@version:component1,...”的方式使用,
# 如果是当前`_PROJECT`的组件之间的依赖,则可以使用“:component1,component2...”的表示方法
# (注意:以上方式的@version均可省略,CMake3.19后version支持版本范围,详情请查阅find_package的使用方法)
# _BINARY_DIR one,指定目标的二进制目录(默认:${CMAKE_BINARY_DIR})
# _BINARY_BIN_DIR one,指定目标的二进制目录的 runtime 目录,相对于`_BINARY_DIR`,也可定义绝对路径(默认:bin)
# _BINARY_LIB_DIR one,指定目标的二进制目录的 library 目录,相对于`_BINARY_DIR`,也可定义绝对路径(默认:lib)
# _INSTALL_DIR one,指定目标的安装目录(默认:${CMAKE_INSTALL_PREFIX})
# _INSTALL_INCLUDE_DIR one,指定目标的安装目录的 include 目录,相对于`_INSTALL_DIR`,也可定义绝对路径(默认:include)
# _INSTALL_BIN_DIR one,指定目标的安装目录的 runtime 目录,相对于`_INSTALL_DIR`,也可定义绝对路径(默认:bin)
# _INSTALL_LIB_DIR one,指定目标的安装目录的 library 目录,相对于`_INSTALL_DIR`,也可定义绝对路径(默认:lib)
# _ADD_LIB_SUFFIX opt,在 library 目录名添加后缀"64",仅64位系统有效
# _EXPORT_EXT_FILES_ mul, 构建目标时导出自定义的附加文件,可以指定一个或多个文件,但要确保`_NAME`是运行时工件,
# 可以是绝对或者相对于`CMAKE_CURRENT_SOURCE_DIR`的路径,
# 指定的最后一个参数是导出的目标路径(指定的文件都导出到此处),可以是绝对或者相对于`_BINARY_DIR`的路径。
# 注意:该关键字不允许在`_EXPORT_EXT_FILES_`、`_EXPORT_EXT_DIRS_`、`_INSTALL_EXT_FILES_`和
# `_INSTALL_EXT_DIRS_`以外的其他`mul`关键字之后
# _EXPORT_EXT_DIRS_ mul, 构建目标时导出自定义的附加目录,可以指定一个或多个目录,但要确保`_NAME`是运行时工件,
# 可以是绝对或者相对于`CMAKE_CURRENT_SOURCE_DIR`的路径,
# 指定的最后一个参数是导出的目标路径(指定的目录都导出到此处),可以是绝对或者相对于`_BINARY_DIR`的路径。
# 注意:该关键字不允许在`_EXPORT_EXT_FILES_`、`_EXPORT_EXT_DIRS_`、`_INSTALL_EXT_FILES_`和
# `_INSTALL_EXT_DIRS_`以外的其他`mul`关键字之后
# _INSTALL_EXT_FILES_ mul, 安装自定义的附加文件,可以指定一个或多个文件,可以是绝对或者相对于`CMAKE_CURRENT_SOURCE_DIR`的路径,
# 指定的最后一个参数是安装的目标路径(指定的文件都安装到此处),可以是绝对或者相对于`_INSTALL_DIR`的路径。
# 注意:该关键字不允许在`_EXPORT_EXT_FILES_`、`_EXPORT_EXT_DIRS_`、`_INSTALL_EXT_FILES_`和
# `_INSTALL_EXT_DIRS_`以外的其他`mul`关键字之后
# _INSTALL_EXT_DIRS_ mul, 安装自定义的附加目录,可指定多个目录,可以是绝对或者相对于`CMAKE_CURRENT_SOURCE_DIR`的路径,
# 指定的最后一项是安装的目标路径(指定的文件都安装到此处),可以是绝对或者相对于`_INSTALL_DIR`的路径
# 注意:该关键字不允许在`_EXPORT_EXT_FILES_`、`_EXPORT_EXT_DIRS_`、`_INSTALL_EXT_FILES_`和
# `_INSTALL_EXT_DIRS_`以外的其他`mul`关键字之后
# _INCLUDE_FILES mul,目标公共标头的文件位置,可以是绝对或相对路径,相对路径相对于 `CMAKE_CURRENT_SOURCE_DIR`,支持生成器表达式(无默认)
# _INCLUDE_DIRS mul,目标公共标头的目录位置,可以是绝对或相对路径,相对路径相对于 `CMAKE_CURRENT_SOURCE_DIR`,支持生成器表达式(无默认)
# _INCLUDE_EXCLUDE_REG one,安装目标公共标头时忽略的文件或目录的完整路径相匹配的正则表达式(无默认)
# _INCLUDE_DESTINATION one,匹配目标的`INSTALL_INTERFACE`包含目录(默认:${_INSTALL_INCLUDE_DIR})
# _DISABLE_INTERFACE opt,禁止把`_INCLUDE_DESTINATION`指定的目录包含到`INSTALL_INTERFACE`中
# _MODE one,安装模式,支持的值: `Runtime | Development`(默认:Development)
# _NAMESPACE one,使用命名空间安装您的目标,不要添加额外的'::'(无默认 | ${_PROJECT})
# _EXPORT_HEADER one,此处设置创建的导出标头的文件绝对或相对路径,相对路径相对于`CMAKE_CURRENT_BINARY_DIR`,
# 其安装位置默认是`_INSTALL_INCLUDE_DIR`。
# 注意,当`_MODE`的值为 Runtime 时,它将只会导出导出标头而不会进行安装导出表头(无默认)
# _EXPORT_MACRO one,导出标头中的宏定义(默认:`${_NAME}_API|${_PROJECT}_${_NAME}_API`,将变为大写)
# _EXPORT_INSTALL_DIR one,导出标头的安装路径,可以是绝对或相对路径,相对路径相对于`_INSTALL_DIR`(默认:${_INSTALL_INCLUDE_DIR})
# _INSTALL_PDB opt,安装PDB文件,仅MSVC有效
# _DISABLE_CONFIG opt,禁用 config文件生成
# _DISABLE_VERSION opt,始终禁用`*-config-version.cmake`文件生成,如果没有基于`_VERSION`关键字值以及没有定义`_NAME`的属性VERSION,
# `*-config-version.cmake` 文件也不会生成
# _CONFIG_TEMPLATE one, 用于生成`*-config.cmake`文件的 config 模板文件
# (默认:生成在内部文件缓存目录的 `PKG_normal-config.cmake.in`或`PKG_components-config.cmake.in`)
# _ADD_UNINSTALL opt,`_IS_COMPONENT`关闭时可用,添加卸载命令,如果定义了`_IS_COMPONENT`,则始终强制`_ADD_UNINSTALL`定义为 FALSE
# _UNINSTALL_TEMPLATE one,`_ADD_UNINSTALL`开启时可用,卸载操作的模板文件(默认:生成在内部文件缓存目录的`PKG_cmake_uninstall.cmake.in`)
# _UNINSTALL_ADDITIONAL mul,`_ADD_UNINSTALL`开启时可用,附加卸载的文件或目录,被附加的文件或目录将在卸载操作进行时一同进行卸载(无默认)
#
# @par 全局变量
# PKG_FILE_CACHE_DIR PKG的内部文件缓存目录,如为空,则函数会取默认值:${CMAKE_CURRENT_BINARY_DIR}/_PKG_cache。
# 该路径可以是绝对路径,也可以是相对`CMAKE_CURRENT_SOURCE_DIR`路径的路径。修改此值会影响 PKG 生成的仅供内部使用的文件的位置
# PKG__BINARY_DIR 如果未自定义 _BINARY_DIR 关键字,则会使用改变量值作为其关键字值。该值会影响附属于`_PROJECT`的组件
# PKG__INSTALL_DIR 如果未自定义 _INSTALL_DIR 关键字,则会使用改变量值作为其关键字值。该值会影响附属于`_PROJECT`的组件
#
# @par 函数导出的变量
# PKG_<_PROJECT>_<_NAME>_EXPORT_HEADER_DIR _IS_COMPONENT 开启时有效,值为导出标头的所在目录(确保 _EXPORT_HEADER 关键字已定义)
# PKG_<_NAME>_EXPORT_HEADER_DIR _IS_COMPONENT 与_IS_COMPONENTS 均关闭时有效,值为导出标头的所在目录(确保 _EXPORT_HEADER 关键字已定义)
其实以上的注释也足以让大家知道怎么使用该脚本,无非就是熟悉一个函数——PKG()。
PKG.cmake才是刚刚起步,有bug也在所难免,希望大家多给建议,在GitHub上多献上您的Issues,我收到信息后就会第一时间进行测试和修正,让PKG.cmake对你更有帮助。