CMake:递归检查并拷贝所有需要的DLL文件

2023-11-09


在这里插入图片描述

1. 目的

在基于 CMake 构建的 C/C++ 工程中,拷贝当前工程需要的每个DLL文件到 Visual Studio 工程的启动路径下, 让可执行目标在运行阶段正常运行,解决“DLL找不到”导致的程序终止、异常退出等问题,解决“每次都要手动拷贝,有时候忘记拷贝”的问题。

举例:

  • OpenCV: 官方预编译版本,包含的 opencv_world.dll, 以及读取某些视频时需要的 opencv_ffmpeg dll 文件
  • windows-pthreads 的 DLL 文件
  • 其他依赖库当中,提供的 DLL 文件

实际上不仅限于 Windows 平台的 DLL, 在 Linux / MacOSX 上也同样有这样的问题,此时只需要增加 .so.dylib 文件后缀的动态库支持即可。

本文给出基于 CMake 语言的解决方案。

2. 设计

整体思路

枚举所有 target, 筛选出 SHARED_LIBRARRY 类型的 target, 获取它们的动态库的路径 shared_library_path, 然后拷贝到用户指定的目录 dstDir.

此外有些 dll 文件并没有被 xxx-config.cmake 等配置文件所配置, 需要额外扫描和拷贝,例如 opencv 预编译库中的 ffmpeg 的 dll 文件。

多层依赖的处理

有时候工程比较复杂, target 至少包括三层, 最后一层是可执行文件, 第二层可能没有DLL,但第二层依赖的第一层则可能存在DLL文件,这就导致枚举 target 时不能只枚举一层。换言之,枚举 target 的过程是一个递归过程, 需要使用深度优先搜索 DFS 算法

获取 DLL 所在目录

这个问题比较简单, 用 cmake 的 get_target_property 函数获取。

探测剩余的 DLL 文件

包括两种情况:

  • target 本身是动态库类型, 那么它的 DLL 文件所在的目录应该被扫描,扫描出的新的 DLL 文件也应该被拷贝
  • target 本身是静态库类型, 但它所在目录的上一级目录中, 有一个 bin 目录, bin 目录里存放有 DLL 文件

3. 代码实现

代码实现过程中遇到一些“难点”,主要是对 cmake 不够足够熟悉, 简单列举:

判断 stack 是否为空

  • DFS 算法的实现过程中, 怎样判断 stack 为空?获取 stack 首部元素?依赖于对 list 的操作, 包括将“列表是否为空”封装为函数
#======================================================================
# Determine if a list is empty
#======================================================================
# Example:
# cvpkg_is_list_empty(testbed_requires testbed_requires_empty)
# message(STATUS "testbed_requires_empty: ${testbed_requires_empty}")
#----------------------------------------------------------------------
function(cvpkg_is_list_empty the_list ret)
  list(LENGTH ${the_list} the_list_length)
  if(${the_list_length} EQUAL 0)
    set(${ret} TRUE PARENT_SCOPE)
  else()
    set(${ret} FALSE PARENT_SCOPE)
  endif()
endfunction()

判断 stack 是否为空

通过判断元素是否在列表中来实现。封装为了函数

#======================================================================
# Determine if item is in the list
#======================================================================
# Example: 
# cvpkg_is_item_in_list(testbed_requires "protobuf" protobuf_in_the_lst)
# message(STATUS "protobuf_in_the_lst: ${protobuf_in_the_lst}")
# 
# cvpkg_is_item_in_list(testbed_requires "opencv" opencv_in_the_lst)
# message(STATUS "opencv_in_the_lst: ${opencv_in_the_lst}")
#----------------------------------------------------------------------
function(cvpkg_is_item_in_list the_list the_item ret)
  list(FIND ${the_list} ${the_item} index)
  if(index EQUAL -1)
    set(${ret} FALSE PARENT_SCOPE)
  else()
    set(${ret} TRUE PARENT_SCOPE)
  endif()
endfunction()

获取所有 target

原本的依赖关系是 hierarchical 的, 怎样拍平,得到一维的依赖列表?并且不能有重复元素?答案是用 DFS。

#======================================================================
# 4. Recursively get required packages for a package. No duplicated.
#======================================================================
# Example: 
# cvpkg_get_flatten_requires(testbed flatten_pkgs)
# message(STATUS "flatten_pkgs: ${flatten_pkgs}")
#----------------------------------------------------------------------
function(cvpkg_get_flatten_requires input_pkg the_result)
  list(LENGTH input_pkg input_pkg_length)
  if(NOT (${input_pkg_length} EQUAL 1))
    cvpkg_error("input_pkg should be single element list")
  endif()

  set(visited_pkgs "")
  set(pkg_stack ${input_pkg})
  while(TRUE)
    cvpkg_is_list_empty(pkg_stack pkg_stack_empty)
    if(${pkg_stack_empty})
      break()
    endif()

    cvpkg_debug("pkg_stack: ${pkg_stack}")
    # pop the last element
    list(POP_BACK pkg_stack pkg)
    cvpkg_debug("pkg: ${pkg}")

    # mark the element as visited
    cvpkg_is_item_in_list(visited_pkgs "${pkg}" pkg_visited)
    if(NOT ${pkg_visited})
      cvpkg_debug(" visiting ${pkg}")
      list(APPEND visited_pkgs ${pkg})

      # traverse it's required dependencies and put into pkg_stack
      get_target_property(subpkgs ${pkg} LINK_LIBRARIES)
      cvpkg_debug("LINK_LIBRARIES: ${subpkgs}")
      if(subpkgs)
        foreach(subpkg ${subpkgs})
          if(TARGET ${subpkg}) # if called target_link_libraries() more than once, subpkgs contains stuffs like `::@(000001FAFA8C75C0)`
            cvpkg_debug("  subpkg: ${subpkg}")
            list(APPEND pkg_stack ${subpkg})
          endif()
        endforeach()
      endif()

      get_target_property(subpkgs ${pkg} INTERFACE_LINK_LIBRARIES)
      cvpkg_debug("INTERFACE_LINK_LIBRARIES: ${subpkgs}")
      if(subpkgs)
        foreach(subpkg ${subpkgs})
          if(TARGET ${subpkg}) # if called target_link_libraries() more than once, subpkgs contains stuffs like `::@(000001FAFA8C75C0)`
            cvpkg_debug("  subpkg: ${subpkg}")
            list(APPEND pkg_stack ${subpkg})
          endif()
        endforeach()
      endif()
    endif()

  endwhile()

  list(POP_FRONT visited_pkgs visited_pkgs)
  set(${the_result} ${visited_pkgs} PARENT_SCOPE)
endfunction()

检测并拷贝 DLL

这是代码最多的函数, 不过思路上前面已经提到过, 并不复杂。

代码多的几个原因:

  • 支持 .dll 的同时, 要支持 .so 和 .dylib
  • windows 上的 target, 可能 debug 和 release 库的文件不是同一个,都需要拷贝,因此需要枚举5个属性
  set(prop_lst "IMPORTED_LOCATION;IMPORTED_LOCATION_DEBUG;IMPORTED_LOCATION_RELEASE")
  • 去重: 拷贝过的文件要忽略, 重复的目录要合并

Talk is cheap, show me the code:

#======================================================================
# Copy imported lib for all build types
# Should only be used for shared libs, e.g. .dll, .so, .dylib
#======================================================================
# Example: 
# cvpkg_copy_imported_lib(testbed ${CMAKE_BINARY_DIR}/${testbed_output_dir})
#----------------------------------------------------------------------
function(cvpkg_copy_imported_lib targetName dstDir)
  set(prop_lst "IMPORTED_LOCATION;IMPORTED_LOCATION_DEBUG;IMPORTED_LOCATION_RELEASE")
  
  if(NOT (TARGET ${targetName}))
    return()
  endif()

  if(CMAKE_SYSTEM_NAME MATCHES "Windows")
    set(shared_library_filename_ext ".dll")
  elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
    set(shared_library_filename_ext ".so")
  elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
    set(shared_library_filename_ext ".dylib")
  endif()

  get_target_property(pkg_type ${targetName} TYPE)
  if(NOT (${pkg_type} STREQUAL "SHARED_LIBRARY"))
    if(${pkg_type} STREQUAL "STATIC_LIBRARY")

      if(CMAKE_SYSTEM_NAME MATCHES "Windows")
        set(static_library_filename_ext ".lib")
      elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
        set(static_library_filename_ext ".a")
      elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
        set(static_library_filename_ext ".a")
      endif()

      ### for static library targets, there might be `bin` directory, parallel to `lib` directory.
      # 先获取静态库文件路径
      foreach(prop ${prop_lst})
        get_target_property(static_library_path ${pkg} ${prop})
        if(static_library_path)
          # 获取静态库所在目录
          get_filename_component(static_library_live_directory ${static_library_path} DIRECTORY)
          # 获取静态库目录的上层目录
          get_filename_component(static_library_parent_directory ${static_library_live_directory} DIRECTORY)
          set(candidate_bin_dir "${static_library_parent_directory}/bin")
          # 判断上层目录是否存在 bin 目录, 如果存在 bin 目录, 执行扫描和拷贝
          if(EXISTS "${candidate_bin_dir}")
            set(glob_pattern "${candidate_bin_dir}/*${shared_library_filename_ext}")
            file(GLOB shared_library_path_lst "${glob_pattern}")
            foreach(shared_library_path ${shared_library_path_lst})
              list(APPEND copied_shared_library_path_lst "${shared_library_path}")
              cvpkg_info("Copy ${shared_library_filename_ext} file (for static library, we detect and copy them!)")
              cvpkg_info("  - shared library file: ${prop}=${static_library_path}")
              cvpkg_info("  - dstDir: ${dstDir}")
              execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${shared_library_path} ${dstDir})
            endforeach()
          endif()
        endif()
      endforeach()
    endif()

    return()
  endif()


  ### copy as the package description file (xxx-config.cmake or xxx.cmake) decribed
  set(pkg ${targetName})
  set(copied_shared_library_path_lst "")
  foreach(prop ${prop_lst})
    cvpkg_debug("!! prop: ${prop}")
    get_target_property(shared_library_path ${pkg} ${prop})
    if(shared_library_path)
      list(APPEND copied_shared_library_path_lst "${shared_library_path}")
      cvpkg_info("Copy ${shared_library_filename_ext} file")
      cvpkg_info("  - package(target): ${pkg}")
      cvpkg_info("  - prop: ${prop}=${shared_library_path}")
      cvpkg_info("  - dstDir: ${dstDir}")
      execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${shared_library_path} ${dstDir})
    endif()
  endforeach()

  ### copy un-tracked shared library files that under same directory of each tracked shared library files
  cvpkg_is_list_empty(copied_shared_library_path_lst copied_shared_library_path_lst_empty)
  if(${copied_shared_library_path_lst_empty})
    return()
  endif()

  # get directories of each copied shared library files
  set(shared_library_live_directory_lst "")
  foreach(copied_shared_library_path ${copied_shared_library_path_lst})
    get_filename_component(shared_library_live_directory ${copied_shared_library_path} DIRECTORY)
    list(APPEND shared_library_live_directory_lst "${shared_library_live_directory}")
  endforeach()

  # remove duplicated directories
  list(REMOVE_DUPLICATES "${shared_library_live_directory_lst}")

  # for each candidate directory, scan shared library files
  foreach(shared_library_live_directory ${shared_library_live_directory_lst})
    set(glob_pattern "${shared_library_live_directory}/*${shared_library_filename_ext}")
    file(GLOB shared_library_path_lst "${glob_pattern}")
    foreach(shared_library_path ${shared_library_path_lst})
      # if the scanned shared library file is not copied, do a copy
      cvpkg_is_item_in_list(copied_shared_library_path_lst "${shared_library_path}" shared_library_already_copied)
      if(NOT shared_library_already_copied)
        list(APPEND copied_shared_library_path_lst "${shared_library_path}")
        cvpkg_info("Copy ${shared_library_filename_ext} file (xxx-config.cmake forget this file, but we copy them!)")
        cvpkg_info("  - package(target): ${pkg}")
        cvpkg_info("  - prop: ${prop}=${shared_library_path}")
        cvpkg_info("  - dstDir: ${dstDir}")
        execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${shared_library_path} ${dstDir})
      endif()
    endforeach()
  endforeach()

endfunction()

4. 使用

从使用的角度非常简单:调用 cvpkg_copy_required_dlls() 函数即可,它的实现代码为:

#======================================================================
# Recursively copy required DLL files into destination directory
#======================================================================
# Example: 
# cvpkg_copy_required_dlls(testbed ${CMAKE_BINARY_DIR})
# cvpkg_copy_required_dlls(testbed ${CMAKE_BINARY_DIR}/${testbed_output_dir})
#----------------------------------------------------------------------
function(cvpkg_copy_required_dlls targetName dstDir)
  cvpkg_get_flatten_requires(testbed flatten_pkgs)
  #cvpkg_debug("flatten_pkgs: ${flatten_pkgs}")
  message(STATUS "flatten_pkgs: ${flatten_pkgs}")
  foreach(pkg ${flatten_pkgs})
   cvpkg_copy_imported_lib(${pkg} ${dstDir})
  endforeach()
endfunction()

调用代码为:

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

CMake:递归检查并拷贝所有需要的DLL文件 的相关文章

  • 属性对象什么时候创建?

    由于属性实际上只是附加到程序集的元数据 这是否意味着属性对象仅根据请求创建 例如当您调用 GetCustomAttributes 时 或者它们是在创建对象时创建的 或者 前两个的组合 在由于 CLR 的属性扫描而创建对象时创建 从 CLR
  • C++:无法使用scoped_allocator_adaptor传播polymorphic_allocator

    我有一个vector
  • 模板类的不明确多重继承

    我有一个真实的情况 可以总结为以下示例 template lt typename ListenerType gt struct Notifier void add listener ListenerType struct TimeListe
  • 如何在没有 Control.Invoke() 的情况下从后台线程修改控件属性

    最近 我们遇到了一些旧版 WinForms 应用程序 我们需要更新一些新功能 在专家测试该应用程序时 发现一些旧功能被破坏 无效的跨线程操作 现在 在您认为我是新手之前 我确实有一些 Windows 窗体应用程序的经验 我不是专家 但我认为
  • SSH 主机密钥指纹与模式 C# WinSCP 不匹配

    我尝试通过 WinSCP 使用 C 连接到 FTPS 服务器 但收到此错误 SSH 主机密钥指纹 与模式不匹配 经过大量研究 我相信这与密钥的长度有关 当使用 服务器和协议信息 下的界面进行连接时 我从 WinSCP 获得的密钥是xx xx
  • Cygwin 下使用 CMake 编译库

    我一直在尝试使用 CMake 来编译 TinyXML 作为一种迷你项目 尝试学习 CMake 作为补充 我试图将其编译成动态库并自行安装 以便它可以工作 到目前为止 我已经设法编译和安装它 但它编译成 dll 和 dll a 让它工作的唯一
  • 使用 Microsoft Graph API 订阅 Outlook 推送通知时出现 400 错误请求错误

    我正在尝试使用 Microsoft Graph API 创建订阅以通过推送通知获取 Outlook 电子邮件 mentions 我在用本文档 https learn microsoft com en us graph api subscri
  • 如何在我的应用程序中使用 Windows Key

    Like Windows Key E Opens a new Explorer Window And Windows Key R Displays the Run command 如何在应用程序的 KeyDown 事件中使用 Windows
  • C# 中可空类型是什么?

    当我们必须使用nullable输入 C net 任何人都可以举例说明 可空类型 何时使用可空类型 https web archive org web http broadcast oreilly com 2010 11 understand
  • 使用 Google Analytics API 在 C# 中显示信息

    我一整天都在寻找一个好的解决方案 但谷歌发展得太快了 我找不到有效的解决方案 我想做的是 我有一个 Web 应用程序 它有一个管理部分 用户需要登录才能查看信息 在本节中 我想显示来自 GA 的一些数据 例如某些特定网址的综合浏览量 因为我
  • C# 用数组封送结构体

    假设我有一个类似于 public struct MyStruct public float a 我想用一些自定义数组大小实例化一个这样的结构 在本例中假设为 2 然后我将其封送到字节数组中 MyStruct s new MyStruct s
  • c# Asp.NET MVC 使用FileStreamResult下载excel文件

    我需要构建一个方法 它将接收模型 从中构建excel 构建和接收部分完成没有问题 然后使用内存流导出 让用户下载它 不将其保存在服务器上 我是 ASP NET 和 MVC 的新手 所以我找到了指南并将其构建为教程项目 public File
  • 使用安全函数在 C 中将字符串添加到字符串

    我想将文件名复制到字符串并附加 cpt 但我无法使用安全函数 strcat s 来做到这一点 错误 字符串不是空终止的 我确实设置了 0 如何使用安全函数修复此问题 size strlen locatie size nieuw char m
  • C 中的位移位

    如果与有符号整数对应的位模式右移 则 1 vacant bit will be filled by the sign bit 2 vacant bit will be filled by 0 3 The outcome is impleme
  • 什么是 C 语言的高效工作流程? - Makefile + bash脚本

    我正在开发我的第一个项目 该项目将跨越多个 C 文件 对于我的前几个练习程序 我只是在中编写了我的代码main c并使用编译gcc main c o main 当我学习时 这对我有用 现在 我正在独自开展一个更大的项目 我想继续自己进行编译
  • EPPlus Excel 更改单元格颜色

    我正在尝试将给定单元格的颜色设置为另一个单元格的颜色 该单元格已在模板中着色 但worksheet Cells row col Style Fill BackgroundColor似乎没有get财产 是否可以做到这一点 或者我是否必须在互联
  • ListDictionary 类是否有通用替代方案?

    我正在查看一些示例代码 其中他们使用了ListDictionary对象来存储少量数据 大约 5 10 个对象左右 但这个数字可能会随着时间的推移而改变 我使用此类的唯一问题是 与我所做的其他所有事情不同 它不是通用的 这意味着 如果我在这里
  • Bing 地图运行时错误 Windows 8.1

    当我运行带有 Bing Map 集成的 Windows 8 1 应用程序时 出现以下错误 Windows UI Xaml Markup XamlParseException 类型的异常 发生在 DistanceApp exe 中 但未在用户
  • 窗体最大化时自动缩放子控件

    有没有办法在最大化屏幕或更改分辨率时使 Windows 窗体上的所有内容自动缩放 我发现手动缩放它是正确的 但是当切换分辨率时我每次都必须更改它 this AutoScaleDimensions new System Drawing Siz
  • 为什么 strtok 会导致分段错误?

    为什么下面的代码给出了Seg 最后一行有问题吗 char m ReadName printf nRead String s n m Writes OK char token token strtok m 如前所述 读取字符串打印没有问题 但

随机推荐

  • C++中模板类的声明和实现分离问题

    有两种方法 第1种 使用 tpp 文件实现类模板的接口与实现的文件分离 在 h文件中放接口 在 tpp文件中放实现 但这种方法得在 h文件中 类的定义下面通过 include包含 tpp 文件 如下 testTemplateClass h文
  • 解决windows的挖矿木马

    问题描述 阿里云服务器报有采矿木马 登录服务器后发现CPU满负荷 无法安装阿里云的安全客户端 安装火绒杀毒软件后 杀毒软件也无法运行 提示文件无法找到 通过任务管理器关闭可疑的svchost exe后 马上就创建了一个新的svchost e
  • ssh命令行远程连接服务器跑程序新手教程

    1 ssh远程连接服务器 2 服务器端配置conda环境 3 上传程序到服务器 4 跑程序 5 修改程序 1 用ssh远程连接服务器 打开命令行 cmd ssh 服务器名称 服务器网址 然后按Enter键 输入密码 注意输入的密码不会在屏幕
  • nginx rewrite二级目录跳转带斜线

    内网地址为http IP1 80 wbalone 外网地址为http IP2 88 wbalone 此时 内外网端口不一致 如果访问外网地址时 输入的为http IP2 88 wbalone 则会跳转为http IP2 80 wbalone
  • 算法高级(28)-递归、分治、动态规划、贪心、回溯、分支限界几大相似算法比较

    在学习算法的过程中 递归 分治 动态规划 贪心 回溯 分支限界这些算法有些类似 都是为了解决大问题 都是把大问题拆分成小问题来解决 但她们之间还是有一些不同之处的 我来给同学们整理一下 一 算法思想 1 递归算法 recursion alg
  • 基于jdk11/jdk8 + Spring全家桶开发的微服务中后台快速开发平台

    项目简介 基于jdk11 jdk8 SpringCloudAlibaba SpringCloud SpringBoot 开发的微服务中后台快速开发平台 专注于多组户 SaaS架构 解决方案 亦可作为普通项目 非SaaS架构 的基础开发框架使
  • Zabbix---2 监控主机CPU使用率

    一 监控CPU空闲率 在添加主机时 由于已经链接了Template OS Linux by Zabbix agent模板 该模板还链接了Template Module Linux CPU by Zabbix agent等若干个其他模板 Te
  • MapStruct、BeanUtils性能比较

    一 MapStruct是什么 MapStruct是一款对象转换工具 主要是用于实体对象 VO DTO之间的转换 同样BeanUtils也是这个作用 二 原理 1 BeanUtils原理 反射 是在运行阶段 至于反射为什么慢 后续我了解再补充
  • 使用IDEA基于MVC和分层模式完成登录和注册

    MVC Model View Controller 1 概念 MVC模式中 M是指业务模型 V是指用户界面 C则是控制器 其中 View的定义比较清晰 就是用户界面 M Model模型 完成具体的业务操作 e g 查询数据库 封装对象 V
  • Gateway网关提示Flipping property: system-server.ribbon.ActiveConnectionsLimit to use NEXT property: niws

    一 问题描述 自己有一个微服务项目 使用了GateWay进行限流处理 但是最近发现 无论如何 系统都登陆不上去了 出现了问题 控制台总是提示Flipping property system server ribbon ActiveConne
  • centos8与centos7有什么区别呢?一文带你了解区别

    作者 小刘在C站 个人主页 小刘主页 努力不一定有回报 但一定会有收获加油 一起努力 共赴美好人生 学习两年总结出的运维经验 以及思科模拟器全套网络实验教程 专栏 云计算技术 小刘私信可以随便问 只要会绝不吝啬 感谢CSDN让你我相遇 目录
  • web3.0的特点、应用和安全问题

    特点 Web 3 0 也称为 Web 3 或 Semantic Web 是互联网的下一阶段 旨在构建更加智能 去中心化和用户友好的网络 Web 3 0的主要内容包括以下几个方面 去中心化 Web3 0 的目标是将互联网从集中式架构转变为去中
  • YOLO V7源码解析

    1 命令行参数介绍 YOLO v7参数与YOLO v5差不多 我就直接将YOLO v5命令行参数搬过来了 偷个懒 weights 初始权重 cfg 模型配置文件 data 数据配置文件 hyp 学习率等超参数文件 epochs 迭代次数 i
  • AndroidStudio 快捷键使用总结大全

    原文转自 http mobile 51cto com aengine 463236 htm 本文介绍了一系列在AndroidStudio中常用的快捷键 希望可以帮助各位在AndroidStudio的使用中更加得心应手 如鱼得水 随心所欲 驾
  • vue踩坑记:把对象中的数据同时赋给了两个变量,改变一个对象的值,另一个对象也变化了

    场景 请求到数据后 将数据data a同时赋给了c和b 这时改变c的时候b的值也会跟着变 为什么会出现这种情况呢 其实 这是一个引用传递而不是值传递 c和b指向的是同一个内存地址 如果想实现改变了c而不改变b的话 我们要怎么操作呢 可以给b
  • VC实现微秒(十万分之一秒)休眠

    VC自带的SDK中为我们提供了一个Sleep函数 此函数的最小单位为毫秒 既千分之一秒 但在实际的应该中 特别是网络数据传输 我们需要更小的休眠单位 微秒 而系统又没有提供相关API 那么我们如何实现微秒 既十万分之一秒 的休眠呢 我们知道
  • 芯片面积估算

    If giving total standard cell gate count all memory macro list including memory type bit width and depth all other macro
  • Socket中出现EOFException错误问题

    java io EOFException at java io ObjectInputStream PeekInputStream readFully ObjectInputStream java 2681 at java io Objec
  • 六、Vite 常用第三方库(axios、mockjs、sass、echars、element-plus、开箱即用)

    文章目录 一 参考 二 vite 创建 Vue 项目工程 2 1 create vite和vite的关系是什么呢 2 2 vue团队希望弱化vite的一个存在感 但是我们去学习的时候不能弱化的 2 3 创建工程 三 第三方库的安装 开箱即用
  • CMake:递归检查并拷贝所有需要的DLL文件

    文章目录 1 目的 2 设计 整体思路 多层依赖的处理 获取 DLL 所在目录 探测剩余的 DLL 文件 3 代码实现 判断 stack 是否为空 判断 stack 是否为空 获取所有 target 检测并拷贝 DLL 4 使用 1 目的