CMake教程——如何为你的项目编写完整的install脚本

  最近在研究vcpkg和cmake的集成如何自建port,发现必须要在项目的cmake支持install,坑很多,网上的信息比较少,错误的引导也很多,事例代码写的洋洋洒洒,很规矩很漂亮,然而在library里的CMakeList.txt里压根没有提供install的步骤脚本,也不知道他是怎么通的,但更多的情况是网友提供的install脚本单单cmake可以find_package()vcpkg不能识别。
  想通过身边的人get到此技能几乎没戏,因为绝大多数人用cmake做开发仅仅停留在用的层面,基本不会考虑写install脚本,这里做一次总结,总结下经历过的坑。

首先,我先把经历过的坑列下:

  1. 头文件和库文件能install了,结果find_package()依然找不到库的定义:
    其实,find_package()能找到库得先找到xxxConfig.cmake或者xxx-config.cmake(如果是基于别人已经编译好的库,则需要手写FindXXX.cmake);

  2. 明明生成了xxxConfig.cmake却依然不能被vcpkg识别,一直提示没有找到xxxConfig.cmake或者xxx-config.cmake(如果不用vcpkg,指定CMAKE_PREFIX_PATH或安装到系统目录,cmake倒是能找到):
    configure_file()生成的XXXConfig.cmake信息不完整,即便config文件生成了也不能被vcpkg识别,必须用configure_package_config_file(), 然而IDE(我用的是CLion)里却又不提示有此API;

  3. 通过find_package()查找库时候会报错:
    库项目里必须指定set(CMAKE_BUILD_TYPE Release), 否则vcpkg下载项目后默认以Debug模式编译项目,然而vcpkg又要求在port.profile里将Debug目录删除,导致托管的库在被使用时候提示找不到debug目录下的so或者lib。

如何编写完整的cmake install脚本:

首先,先呈现下一般cmake library项目的结构组成部分:

├── CMakeLists.txt
├── include
│   └── hello.h
│   └── world.h
│   └── ccc.h
├── source
│   └── CMakeLists.txt
│   └── hello.cpp
│   └── world.cpp
├── tests
│   └── CMakeLists.txt
│   └── test1.cpp
│   └── test2.cpp
├── example
│   └── CMakeLists.txt
│   └── example1.cpp
│   └── example1.cpp
└── source
    └── ReadMe.md

以下呈现source目录中的CMakeList.txt的通用脚本:

project(xxx VERSION 1.0.0)

aux_source_directory(. DIR_SRCS)
add_library(${PROJECT_NAME} SHARED ${DIR_SRCS})

target_include_directories(${PROJECT_NAME} PUBLIC
    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

# ------------------------------- install script -------------------------------
# Set version infos target properties.
set_target_properties(${PROJECT_NAME} PROPERTIES
    PUBLIC_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/include/logger.h
    VERSION ${PROJECT_VERSION}
    SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    ARCHIVE FALSE # means don't generate static lib.
)

# 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 AnyNewerVersion
)

# Create config file.
include(CMakePackageConfigHelpers)
configure_package_config_file(
    cmake/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}
)

# Exporting Targets from the Build Tree.
install(EXPORT ${PROJECT_NAME}Targets
    FILE ${PROJECT_NAME}Targets.cmake
    DESTINATION lib/cmake/${PROJECT_NAME}
    NAMESPACE ${PROJECT_NAME}::
)

# 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
)

Config.cmake.in内容如下:

@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")

# find_dependency(OtherLib REQUIRED)

check_required_components("@PROJECT_NAME@")

文件放在跟install script的CMakeList.txt同级目录下

如何集成到项目中

find_package(xxx 1.0.0 CONFIG REQUIRED)
target_link_libraries(main PRIVATE xxx)

如果想在一个CMakeLists.txt生成带component的target,模板如下:

# 定义多个 target
add_library(target1 ...)
add_library(target2 ...)

# 安装 target1
install(TARGETS target1
    EXPORT ${PROJECT_NAME}Targets
    LIBRARY DESTINATION lib COMPONENT target1
    ARCHIVE DESTINATION lib COMPONENT target1
    RUNTIME DESTINATION bin COMPONENT target1
    PUBLIC_HEADER DESTINATION include COMPONENT target1
)

# 安装 target2
install(TARGETS target2
    EXPORT ${PROJECT_NAME}Targets
    LIBRARY DESTINATION lib COMPONENT target2
    ARCHIVE DESTINATION lib COMPONENT target2
    RUNTIME DESTINATION bin COMPONENT target2
    PUBLIC_HEADER DESTINATION include COMPONENT target2
)

# 导出 target1 并添加命名空间
install(EXPORT ${PROJECT_NAME}Targets
    FILE ${PROJECT_NAME}Targets-target1.cmake
    NAMESPACE ${PROJECT_NAME}::
    DESTINATION lib/cmake/${PROJECT_NAME}
    COMPONENT target1
)

# 导出 target2 并添加命名空间
install(EXPORT ${PROJECT_NAME}Targets
    FILE ${PROJECT_NAME}Targets-target2.cmake
    NAMESPACE ${PROJECT_NAME}::
    DESTINATION lib/cmake/${PROJECT_NAME}
    COMPONENT target2
)

# 生成 Config 文件
include(CMakePackageConfigHelpers)
configure_package_config_file(
    ${PROJECT_SOURCE_DIR}/cmake/Config-target1.cmake.in
    ${PROJECT_NAME}Config-target1.cmake
    INSTALL_DESTINATION lib/cmake/${PROJECT_NAME}
)
configure_package_config_file(
    cmake/Config-target2.cmake.in
    ${PROJECT_NAME}Config-target2.cmake
    INSTALL_DESTINATION lib/cmake/${PROJECT_NAME}
)

# 安装 Config 文件
install(
    FILES
        ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config-target1.cmake
        ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion-target1.cmake
    DESTINATION lib/cmake/${PROJECT_NAME}
    COMPONENT target1
)

install(
    FILES
        ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config-target2.cmake
        ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion-target2.cmake
    DESTINATION lib/cmake/${PROJECT_NAME}
    COMPONENT target2
)

在踩坑的道路上,终归我是活着上岸了,不过也要感谢网友的文章,虽然它们带我下海,很多没有带我上岸:

  1. CSDN: cmake 生成供find_package使用的自定义模块
    这篇算是启蒙之篇,感谢作者带我上路,但是它真的不支持vcpkg识别。

  2. 知乎: CMake之install方法的使用
    篇幅写的很好,代码也很规矩,但是真的不能被vcpkg识别。

  3. CSDN: CMAKE_INSTALL_PREFIX无效的解决方案

  4. 简书: CMake最佳实践
    这篇文章的xxxConfig.cmake生成走了弯路,心疼下作者。

  5. CMake库打包以及支持find_package
    这篇文章接近上岸了,但是xxxConfigVersion.cmake没必要自己写,可以自动生成的,心疼下作者。

  6. Tutorial: Easily supporting CMake install and find_package()这篇文章讲解也非常清晰,可参考性比较高。

  7. GitHub: package-example
    这篇文章帮我上岸了,但我也给它简化了一些步骤。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 我在拥抱迟暮的夕阳, 任人们无限的慨叹与欣赏。 我在人群熙攘中旅步在人群熙攘外, 孩子般踮着脚步采你陈酿朝夕的光。...
    洛水无月阅读 367评论 0 0
  • 《怦然心动》这部电影以早恋为主线,一对小孩,一棵树,特别简单的故事,两人是同班同学,小女孩茱莉一直想方设法接近小男...
    秦沐瑶阅读 563评论 2 3
  • 阿塔华尔巴是一个统治着人口超过1000万,面积达200万平方公里大帝国的皇帝。有一天有人告诉他一个叫皮萨罗的人带着...
    去病先生阅读 239评论 0 0
  • 作者: 简.尼尔森 教育学博士, 杰出的心理学家、教育家, 美国"正面管教协会"的创始人. 她是7个孩子的母亲, ...
    Rose宇轩Mom阅读 594评论 0 1