当前位置: 首页 > news >正文

C++ Mac 打包运行方案(cmake)

文章目录

  • 背景
  • 动态库梳理
  • 打包方案
    • 静态库处理
    • 动态库处理(PCL库)
    • 编译链接
    • 动态库后处理逻辑
  • 批量信任

背景

使用C++编写的一个小项目,需要打包成mac下的可执行文件(免安装版本),方便分发给其他mac执行,需要把项目的动态库都打在软件包中,分发之后可以直接运行,而不需要再重复安装。

动态库梳理

经过依赖精简和梳理,项目最终必须依赖的动态库包括:pcl, bzip2, lz4, yaml, rosbag(用于读取rosbag包,后续会有专门的文章会提到如何做到不依赖ros环境)。 在Mac上,这些动态库都已经通过homebrew工具安装好。

brew install cmake bzip2 lz4 pcl

brew 在安装bzip2lz4 时,也会默认安装他们的静态库文件(liblz4.alibbz2.a),而pcl库是直接安装的动态库文件。

打包方案

yaml动态库在前面的文章中已经转成了静态库代码的形式包含在了项目里。对于动态库,和linux类似,需要把动态库(dylib)直接打包到存放可执行文件的目录中。不过,不同于linux,mac上需要处理动态库自身的动态库依赖。

由于我们使用homebrew安装依赖库,所以我们在编译时(CMakeLists.txt)中需要在brew的安装路径下查找定位依赖库。

# Homebrew 安装路径
set(HOMEBREW_PREFIX "/usr/local/opt")

静态库处理

对于bzip2lz4,我们在编译的时候直接依赖他们的静态库文件,这样比较方便。

# lz4, bzip2 静态库路径
set(LZ4_LIBRARY "${HOMEBREW_PREFIX}/lz4/lib/liblz4.a")
set(BZIP2_LIBRARIES "${HOMEBREW_PREFIX}/bzip2/lib/libbz2.a")find_package(BZip2 REQUIRED)
# 显式指定 BZip2 静态库(覆盖默认动态库)
if(BZIP2_FOUND)set(BZIP2_LIBRARIES "${HOMEBREW_PREFIX}/bzip2/lib/libbz2.a")message(STATUS "BZip2 static lib: ${BZIP2_LIBRARIES}")
endif()# 定位 lz4 静态库
find_library(LZ4_LIBRARY_RELEASE NAMES liblz4.a PATHS "${HOMEBREW_PREFIX}/lz4/lib" REQUIRED)
find_library(LZ4_LIBRARY_DEBUG NAMES liblz4d.a PATHS "${HOMEBREW_PREFIX}/lz4/lib")
if(LZ4_LIBRARY_RELEASE)set(LZ4_LIBRARIES ${LZ4_LIBRARY_RELEASE})if(LZ4_LIBRARY_DEBUG)set(LZ4_LIBRARIES optimized ${LZ4_LIBRARY_RELEASE} debug ${LZ4_LIBRARY_DEBUG})endif()message(STATUS "lz4 static lib found: ${LZ4_LIBRARIES}")
endif()

动态库处理(PCL库)

# PCL 动态库路径
set(PCL_DIR "${HOMEBREW_PREFIX}/pcl/lib/cmake/pcl")  
list(APPEND CMAKE_PREFIX_PATH "${PCL_DIR}")# macOS 动态库运行时搜索路径设置
set(CMAKE_INSTALL_RPATH "@executable_path")
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)# 框架链接(PCL 可能需要的系统框架)
link_libraries("-framework Accelerate" "-framework OpenGL" "-framework Foundation")find_package(PCL REQUIRED COMPONENTS io)
if(PCL_FOUND)message(STATUS "PCL Found: ${PCL_LIBRARIES}")include_directories(${PCL_INCLUDE_DIRS})
else()message(FATAL_ERROR "PCL not found!")
endif()

编译链接

add_executable(MyApp ...)target_include_directories(MyApp PUBLIC ${PCL_INCLUDE_DIRS}${CMAKE_SOURCE_DIR}/thirdparty/yaml-cpp/include...
)# 链接配置:PCL动态库 + 其他静态库
target_link_libraries(MyApp PUBLIC ${PCL_LIBRARIES}       # PCL动态库yaml-cpp               # 静态源码编译的yaml-cpp${BZIP2_LIBRARIES}     # BZip2静态库${LZ4_LIBRARIES}       # lz4静态库
)

动态库后处理逻辑

首先,可以使用 otool -L 提取依赖的动态库,不管是App还是动态库本身,都可以用这个命令来定位到他们的依赖。这里和linux不同的地方在于,需要递归去处理动态库及动态库自身的依赖。针对以下两种情况分别处理:

  1. 动态库路径中包含 /usr/local :直接复制对应地址的动态库到目标路径的libs下(dist/libs/

  2. 动态库路径中包含 @rpath:这种情况比较麻烦,因为根据@rpath并不能定位到动态库的路径,需要根据动态库类型替换@rpath为实际的动态库路径:

if echo "$dep" | grep -q "vtk"; thenreplaced_str=${dep//@rpath//usr/local/opt/vtk/lib}echo $replaced_strcp -L "$replaced_str" dist/libs/

把动态库拷贝到目标路径后,就可以递归调用相同的方法,定位拷贝动态库自身的依赖。从递归返回之后,就可以修改当前动态库依赖的路径为新的目标路径,即dist/libs/

new_dep_path="@executable_path/../libs/$dep_name"
install_name_tool -change "$dep" "$new_dep_path" "$lib" 

完整脚本如下:

#!/bin/bash
set -erm -rf build && mkdir build && cd build
cmake .. -DBP_USE_PCL=ON
make -j$(nproc)process_deps() {local lib=$1echo "process lib $1"# 提取当前库的依赖项otool -L "$lib" | awk 'NR>1 && /^\t/ {print $1}' | grep -E "/usr/local|@rpath" |while read dep; do# 复制依赖库到 libs/(如果尚未存在)dep_name=$(basename "$dep")if [ ! -f "dist/libs/$dep_name" ]; thenif echo "$dep" | grep -q "vtk"; thenreplaced_str=${dep//@rpath//usr/local/opt/vtk/lib}echo $replaced_strcp -L "$replaced_str" dist/libs/elif echo "$dep" | grep -q "Qt"; thenreplaced_str=${dep//@rpath//usr/local/opt/qt/lib/}echo $replaced_strcp -L "$replaced_str" dist/libs/elif echo "$dep" | grep -q "pcl"; thenreplaced_str=${dep//@rpath//usr/local/opt/pcl/lib/}echo $replaced_strcp -L "$replaced_str" dist/libs/elif echo "$dep" | grep -q "boost"; thenreplaced_str=${dep//@rpath//usr/local/opt/boost/lib/}echo $replaced_strcp -L "$replaced_str" dist/libs/elsecp -L "$dep" dist/libs/fichmod +w dist/libs/"$dep_name"  # 确保可修改权限process_deps "dist/libs/$dep_name"  # 递归处理fi# 修改当前库的依赖路径new_dep_path="@executable_path/../libs/$dep_name"install_name_tool -change "$dep" "$new_dep_path" "$lib" done
}# 1. 初始化目录
rm -rf dist
mkdir -p dist/{libs,bin}# 2. 拷贝可执行文件
cp MyApp dist/bin/
cp ../mac-prepare.sh dist/
chmod +x dist/mac-prepare.sh
APP_PATH="dist/bin/MyApp"
process_deps "$APP_PATH"
echo "Bundle created in dist/"

批量信任

迁移到其他mac上执行时,会提示不可信任的开发者,而且不只是可执行文件会提示,每个动态库都会提示,人工处理显然处理不过来。

在这里插入图片描述

所以,我们要在目标mac上先进行批量信任才能正常运行该可执行文件。

#!/bin/bash
set -e# 目标机器上执行
BIN_DIR="./bin"
LIB_DIR="./libs"# # 步骤1:递归移除所有隔离属性
echo "[1/3] Removing quarantine attributes..."
xattr -dr com.apple.quarantine "$BIN_DIR"
xattr -dr com.apple.quarantine "$LIB_DIR"# # 步骤2:递归重新签名
echo "[2/3] Re-signing binaries and libraries..."
find "$LIB_DIR" \-type f \( -name "*.dylib" -o -name "*.so" -o -perm +111 \) \-exec codesign --force --sign - {} \; 2>/dev/null

相关文章:

  • 工业大数据的定义
  • 国产数据库工具突围:SQLynx如何解决Navicat的三大痛点?深度体验报告
  • SQL优化总结
  • TASK02【Datawhale 组队学习】使用 LLM API 开发应用
  • 一:操作系统概述之操作系统发展历史和分类
  • string(c++)
  • Vscode 配置python调试环境
  • django中用 InforSuite RDS 替代memcache
  • Java实现MinIO上传PDF文件并配置浏览器在线打开及vue2上传页面
  • Vue3+ElementPlus 开箱即用后台管理系统,支持白天黑夜主题切换,通用管理组件,
  • 开启智能未来:DeepSeek赋能行业变革之路
  • 【数据处理】Python对CMIP6数据进行插值——详细解析实现(附源码)
  • Java基础(网络编程)
  • 今日行情明日机会——20250515
  • ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal 详解与使用场景
  • 初始化一个Springboot项目
  • linux libdbus使用案例
  • 双目立体视觉
  • 紫外相机工作原理及可应用范围
  • 介绍一下什么是 AI、 AGI、 ASI
  • 央视起底“字画竞拍”网络传销案:涉案44亿元,受害者众多
  • 在本轮印巴冲突的舆论场上也胜印度一筹,巴基斯坦靠什么?
  • 韧性十足的中国外贸企业:“不倒翁”被摁下去,还会再弹起来
  • 新任美国驻华大使庞德伟抵京履职,外交部回应
  • 呼吸医学专家杜晓华博士逝世,终年50岁
  • 俄官员说将适时宣布与乌克兰谈判代表