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

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
http://www.xdnf.cn/news/6337.html

相关文章:

  • 论文中表格跨页该怎么整(如何给跨页表格添加标题和表头)
  • nestjs[一文学懂TypeORM在nestjs中的日常使用]
  • RabbitMQ 消息模式实战:从简单队列到复杂路由(二)
  • #跟着若城学鸿蒙# 鸿蒙-卡证识别
  • 《Deepseek从入门到精通》清华大学中文pdf完整版
  • Python训练打卡Day22
  • 【AI论文】对抗性后期训练快速文本到音频生成
  • 监控易运维管理软件:日志监控,化繁为简
  • 【SPIN】用Promela验证顺序程序:从断言到SPIN实战(SPIN学习系列--2)
  • 代驾小程序订单系统框架搭建
  • 从基础到实习项目:C++后端开发学习指南
  • OkHttp用法-Java调用http服务
  • ERP系统如何做好工厂生产管理?4种ERP先进生产管理模式分享!
  • [Linux]从零开始的STM32MP157 Busybox根文件系统测试及打包
  • AutoVACUUM (PostgreSQL) 与 DBMS_STATS.GATHER_DATABASE_STATS_JOB_PROC (Oracle) 对比
  • 第六章: SEO与交互指标 二
  • 2025年5月AI科技领域周报(5.5-5.11):AGI研究进入关键验证期 具身智能开启物理世界交互新范式
  • 20250515配置联想笔记本电脑IdeaPad总是使用独立显卡的步骤
  • python 如何遍历 postgresql 所有的用户表 ?
  • Oracle-相关笔记
  • python中使用neo4j
  • LeetCode 45. 跳跃游戏 II(中等)
  • 牛客网NC22015:最大值和最小值
  • 【Linux系列】Linux 系统下 SSD 磁盘识别
  • 二、xlib事件
  • tomcat项目重构踩坑易错点
  • 【RAP】RAP动作与流行舞蹈/街舞
  • 基于昇腾300IDUO 部署PaddleOCR模型
  • mock 数据( json-server )
  • React学习———Redux 、 React Redux和react-persist