【cmake】利用ExternalProject_Add解决第三方库target命名冲突问题

2023-05-16

参考
cmake菜谱第八章第一节

我们经常会遇到这种情况:

project A 是最外层项目
project B 是A使用的外部库
project C 是A和B使用的外部库

.
├── extern
│   ├── B
│   │   ├── extern
│   │   │   └── C
│   │   └── src
│   └── C
└── src

这就导致project C被引用了两遍,从而出现target命名冲突的问题。由于target均是全局的,因此一旦冲突就很难解决。

解决方案有以下几种:

  1. 使用if(NOT target C)来保证target C只编译了一遍。缺点在于A中和B中C的版本和配置也许不同,但不得不使用同一个。
  2. 重命名A和B中的targetC。比如改名为A_C和B_C。缺点是你需要魔改太多东西。
  3. 使用超级构建,例如ExternalProject_Add。

我们这里重点讨论第三种

ExternalProject_add解决target冲突

问题描述

首先我们要来看一下没有解决前的报错是什么样的:

报错如下:

[cmake] CMake Error at E:/codes/vcpkg/scripts/buildsystems/vcpkg.cmake:582 (_add_executable):
[cmake]   _add_executable cannot create target "Cexe" because another target with the
[cmake]   same name already exists.  The existing target is an executable created in
[cmake]   source directory "E:/codes/try/cmake-target-clash/extern/B/extern/C".  See
[cmake]   documentation for policy CMP0002 for more details.
[cmake] Call Stack (most recent call first):
[cmake]   extern/C/CMakeLists.txt:9 (add_executable)
[cmake] 
[cmake] 
[cmake] -- Configuring incomplete, errors occurred!
[cmake] See also "E:/codes/try/cmake-target-clash/build/CMakeFiles/CMakeOutput.log".
[cmake] CMake Error at E:/codes/vcpkg/scripts/buildsystems/vcpkg.cmake:623 (_add_library):
[cmake]   _add_library cannot create target "Clib" because another target with the
[cmake]   same name already exists.  The existing target is a static library created
[cmake]   in source directory "E:/codes/try/cmake-target-clash/extern/B/extern/C".
[cmake]   See documentation for policy CMP0002 for more details.
[cmake] Call Stack (most recent call first):
[cmake]   extern/C/CMakeLists.txt:10 (add_library)
[cmake] 
[cmake] 
[proc] The command: "C:\Program Files\CMake\bin\cmake.EXE" --no-warn-unused-cli -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -Se:/codes/try/cmake-target-clash -Be:/codes/try/cmake-target-clash/build -G "Visual Studio 16 2019" -T host=x64 -A x64 exited with code: 1 and signal: null

重点是这句

_add_executable cannot create target “Cexe” because another target with the same name already exists. The existing target is an executable created in source directory “E:/codes/try/cmake-target-clash/extern/B/extern/C”.

显然,这告诉我们已经存在了一个Cexe目标。也就是因为嵌套引入C而导致的target重名冲突。

解决方案: 使用ExternalProject_Add添加外部库

# 使用ExternalProject可以解决target冲突问题
include(ExternalProject)
set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/ep_base_I_set)

ExternalProject_Add(External_C_name_I_set
  SOURCE_DIR
    ${CMAKE_CURRENT_LIST_DIR}/extern/C
  CMAKE_ARGS
    -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
    -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
  INSTALL_COMMAND
    ""
  )

ExternalProject_Add(External_B_name_I_set
  SOURCE_DIR
    ${CMAKE_CURRENT_LIST_DIR}/extern/B
  CMAKE_ARGS
    -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
    -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
  INSTALL_COMMAND
    ""
  )

首先要引入ExternalProject_Add这个cmake函数。这是一个cmake官方内置的函数。

接着为当前目录设置一下EP_BASE这个属性,这就代表ExternalProject Base。也就是外部项目的根目录。配置后会自动在该目录之下创建Build, Download, Install, Stamp和tmp文件夹。
在这里插入图片描述
(注:我们也可以通过在ExternalProject_add指定PREFIX参数来指定外部库的根目录。如果什么都不指定,那么默认为<name>-prefix 其中name就是该外部库的名字。)

然后我们利用ExternalProject_add这个函数

解释下这函数的参数:

  • 第一个参数代表你给定该外部项目的target名称(如External_B_name_I_set)
  • SOURCE_DIR代表你要指定的源码位置
  • CMAKE_ARGS代表你从外界传入的命令行参数
  • INSTALL_COMMAND 为空保证了不会报出如下错误。由于默认会执行install,所以会报下面的错误。
[build] MSBUILD : error MSB1009: 项目文件不存在。 [E:\codes\try\cmake-target-clash\build\B.vcxproj]
[build]   开关:install.vcxproj
[build] E:\App\Microsoft Visual 

当你配置cmake后,会出现如下几个目标
在这里插入图片描述

其中B和C都是UTILITY目标,这些目标是不能被直接使用的。(因此我们后面要手动将其设置为可以使用的目标)

外部项目就像存在于一个独立封闭的空间,除了你自己在ExternalProject_Add中定义的 target名之外,什么都不会暴露出来。

我们这时候编译External_B_name_I_set,不会出现任何target冲突的错误

UTILITY目标不可链接

上面说了。编译出来的是UTILITY目标,这些目标无法被直接使用。需要手动去设置将其变为正常的可链接的目标。

如果这时候直接去link会报错。

target_link_libraries(A PRIVATE
External_C_name_I_set
External_B_name_I_set
)

报错如下所示

[cmake] CMake Error at CMakeLists.txt:48 (target_link_libraries):
[cmake]   Target "External_B_name_I_set" of type UTILITY may not be linked into
[cmake]   another target.  One may link only to INTERFACE, OBJECT, STATIC or SHARED
[cmake]   libraries, or to executables with the ENABLE_EXPORTS property set.

它告诉我们UTILITY目标是个假目标,是不能被link的。

题外话:

假目标就是用来执行某些命令的假目标,比如add_custom_target当中的目标都是假目标。

不信我们可以定义一个假目标看看。

add_custom_target(hello_target
  COMMAND ${CMAKE_COMMAND} -E echo "hello I am a custom target"
  )

在这里插入图片描述

ZERO_CHECK也是个假目标。ZERO_CHECK就是保证更改了cmake文件后编译时自动config。这个伪目标是cmake内置的。

解决UTILITY不可链接错误

如何解决呢?

  1. 比较原始的方法,根据库的地址链接库。(由于我们这个例子比较简单,我们就不演示头文件了)。

  2. 就是调用install命令。这种比较好,但是要求你所用的第三方库作者写了install命令(一般都会写)。

首先演示方案1:

ExternalProject_Get_Property(External_B_name_I_set BINARY_DIR )
message(STATUS "BINARY_DIR: ${BINARY_DIR}")
target_link_libraries(A PRIVATE
${BINARY_DIR}/$<CONFIG>/Blib.lib
)
unset(BINARY_DIR)

没什么可说的,就是找到Blib.lib的绝对地址然后链接而已。

其次演示方案2:

首先去掉INSTALL_COMMAND “”

为B和C编写install命令

cmake_minimum_required(VERSION 3.23)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

project(A)

add_executable(A src/main.cpp)

# 直接add_subdirectory会导致target冲突
# add_subdirectory(extern/B)
# add_subdirectory(extern/C)

# 使用ExternalProject可以解决target冲突问题
include(ExternalProject)
set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/ExternalProject)

set(EXT_INALL_DIR ${CMAKE_BINARY_DIR}/ExternalProject/Install)
message(STATUS "EXT_INALL_DIR: ${EXT_INALL_DIR}")

ExternalProject_Add(External_C
  SOURCE_DIR
    ${CMAKE_CURRENT_LIST_DIR}/extern/C
  CMAKE_ARGS
    -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
    -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
    -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
    -DCMAKE_INSTALL_PREFIX:PATH=${EXT_INALL_DIR}/External_C 
  )

ExternalProject_Add(External_B
  SOURCE_DIR
    ${CMAKE_CURRENT_LIST_DIR}/extern/B
  CMAKE_ARGS
    -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
    -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
    -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
    -DCMAKE_INSTALL_PREFIX:PATH=${EXT_INALL_DIR}/External_B 
  )

# 方案1(INSTALL_COMMAND "")
# ExternalProject_Get_Property(External_B BINARY_DIR )
# message(STATUS "BINARY_DIR: ${BINARY_DIR}")
# target_link_libraries(A PRIVATE
# ${BINARY_DIR}/$<CONFIG>/Blib.lib
# )
# unset(BINARY_DIR)


# # 方案2
# 可以直接链接并include
ExternalProject_Get_Property(External_B INSTALL_DIR)
set(BLIB_INCLUDE_DIR ${INSTALL_DIR}/include)
set(BLIB_LIBRARIES ${INSTALL_DIR}/lib/Blib.lib)
unset(INSTALL_DIR)
# target_link_libraries(A PRIVATE
#   ${BLIB_LIBRARIES}
# )
# target_include_directories(A PUBLIC
#   ${BLIB_INCLUDE_DIR}
# )

# 也可以先封装成一个INTERFACE库,然后链接
add_library(External_B_to_link INTERFACE)
target_link_libraries(External_B_to_link INTERFACE
  ${BLIB_LIBRARIES}
)
target_include_directories(External_B_to_link INTERFACE
  ${BLIB_INCLUDE_DIR}
)
target_link_libraries(A PRIVATE
  External_B_to_link
)
add_dependencies(External_B_to_link External_B)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【cmake】利用ExternalProject_Add解决第三方库target命名冲突问题 的相关文章

  • 使用数据表反向生成以及出现问题汇总

    什么是正向以及反向生成 正向生成 xff0c 指的是先创建model py文件 xff0c 然后通过django内置的编译器 xff0c 在数据库如mysql中创建出符合model py的表 反向生成 xff0c 指的是先在数据库中crea
  • 解决svn文件图标不显示

    1 首先检查svn设置中是否设置图标显示 2 win 43 R xff0c 输入regedit xff0c 调出注册表信息 xff0c 按下Ctrl 43 F 在注册表里搜索 ShellIconOverlayIdentifiers 查看是否
  • vue父组件传值给子组件不生效

    先上代码 父组件 lt hj subjects v if 61 34 showHjSubect 34 list 61 34 subjectsList 34 64 getHjSubjects 61 34 getHjSubject 34 gt
  • vue下载pdf为空问题解决

    后端返回文件流 xff0c vue下载到本地 请求时需要设置responseType blob 否则下载pdf文件打开会为空 下载方法代码如下 xff1a url请求参数 params请求参数 filename文件名称 span class
  • vue使用iframe嵌入html

    1 本地html文件的存放 在根目录下找到public文件夹 xff0c 在public文件夹下创建static文件夹 xff0c 把html文件放入static文件夹中 2 使用iframe标签引入html文件 span class to
  • 解决Android运行过程中出现的NoClassDefFoundError

    1 先说一下出现上述问题的经过 出现这个问题是集成一个aar之后 xff0c 编译过程中没有问题 在安装运行的时候出现上述错误 然后百度各种解决办法都无济于事 但是有了一点思路 报错位置 在自定义的application中初始化的aar导致
  • 前端将base64图片格式转化为文件流并传给后端

    base64图片格式 xff1a base64图片格式转化为文件流代码 xff1a data base64图片格式字符串 filename xff1a 文件名称 base64toFile span class token punctuati
  • IP地址分类

    一 xff1a IP地址 1概念 xff1a a IP地址是指互联协议地址 xff0c 又译为网际协议地址 b IP地址是提供的IP协议一种统一地址格式 xff0c 它为互联网上的每一个网络和每一台主机分配一个逻辑地址 xff0c 以此来屏
  • vue项目打包npm run build报错

    报错如下图 xff1a 删除根目录下的package lock json和node modules文件 xff0c 再运行npm i命令 npm i命令运行完成后 xff0c 再输入npm run build命令 命令执行成功后 xff0c
  • SpringBoot运行报o.s.b.d.LoggingFailureAnalysisReporter

    报错截图 xff1a 报错原因 xff1a 从上图Description中可以看出 xff0c 报错原因是端口被占用 报错解决方法 xff1a 找到application yml文件 xff0c 修改端口为8081 xff08 或者关闭80
  • ROS教程 Gazebo仿真(3)-摄像头

    接上一篇 ROS教程 Gazebo仿真 2 激光雷达 https blog csdn net weixin 43928944 article details 115904044 配置摄像头 camera sensors xacro span
  • Ubuntu18 远程桌面 VNC-Server 配置[亲测]

    安装vino sudo apt update sudo apt install vino 设Enable VNC 服务 sudo ln span class token operator span s span class token pu
  • redis redisson 集合使用示例(RList、Rset、RMap)

    redis redisson 集合操作 相关类及接口 Rlist xff1a 链表 public interface RList lt V gt extends List lt V gt RExpirable RListAsync lt V
  • Python数据分析、挖掘常用工具

    Python语言 xff1a 简要概括一下Python语言在数据分析 挖掘场景中常用特性 xff1a 列表 xff08 可以被修改 xff09 xff0c 元组 xff08 不可以被修改 xff09 字典 xff08 lt k v gt 结
  • 17个新手常见Python运行时错误

    对于刚入门的Pythoner在学习过程中运行代码是或多或少会遇到一些错误 xff0c 刚开始可能看起来比较费劲 随着代码量的积累 xff0c 熟能生巧当遇到一些运行时错误时能够很快的定位问题原题 下面整理了常见的17个错误 xff0c 希望
  • Python 正在吞噬世界...

    Python is eating the world 这篇关于Python长文火了 从Python的创建过程 xff0c 到Python成为一个无所不在的语言 xff0c 究竟经历了一个怎样的过程 xff1f 美国科技媒体ZDNet记者Ni
  • 解决android studio错误提示信息乱码问题

    android studio在编译过程中 xff0c 如果出现错误会在messages界面给出提示 xff0c 但是有的时候会出现乱码的问题 导致根本看不出来是什么问题 这时候只要做以下设置就能解决乱码问题 xff0c IDE也会将出现的错
  • Python为什么是编程语言中最skr的?

    Python的出现让计算机编程语言不再是生僻的专业技能 xff0c 而是常人都能学习和使用的万金油 经济学人 xff08 Economist xff09 近日对Python的一篇专题报道 xff0c 揭秘了这一把计算机思维带入寻常百姓家的神
  • PLC有几种编程语言?各有什么特点?

    之前我们简单学习了PLC的一些基本知识 今天我们再来了解PLC的编程语言吧 IEC 1131 3的编程语言是IEC工作组对世界范围的PLC厂家的编程语言合理地吸收 借鉴的基础上形成的一套针对工业控制系统的国际编程语言标准 xff0c 它不但
  • 几种主流编程语言

    著名风险投资家Marc Andreessen曾说 xff1a 软件正在吞噬整个世界 没错 xff0c 开发软件的公司越来越多了 xff0c 会编程的人也越来越多了 xff0c 现在的世界上光编程语言就达成千上万种 xff0c 尽管这其中只有

随机推荐