如何在Wheel包中随自研共享库一同打包第三方共享库?
当然可行!这在Python扩展打包场景里太常见了——尤其是当你的C++扩展依赖外部共享库时,总不能让用户自己去装那些库对吧?下面给你几种实用的实现方案:
方法一:手动指定要包含的共享库
如果你的第三方.so库位置固定,最简单的方式是通过setuptools的package_data配合MANIFEST.in来配置:
步骤1:整理项目结构
假设你的项目结构如下:
my_package/ ├── __init__.py ├── my_extension.so # 你编译好的扩展 └── libs/ └── thirdparty.so # 要打包的第三方库
步骤2:修改setup.py
在setup()函数里添加package_data配置,告诉setuptools要包含哪些文件:
from setuptools import setup, find_packages setup( name="my_package", version="0.1.0", packages=find_packages(), # 指定要包含的共享库 package_data={ "my_package": ["libs/*.so"], }, )
步骤3:创建MANIFEST.in
在项目根目录创建这个文件,确保打包时能捕获到这些.so文件:
include my_package/libs/*.so
方法二:自动收集扩展依赖的共享库
如果你的C++扩展是动态链接第三方.so的,可以通过自定义build_ext命令,自动找出依赖的库并复制到包目录里,不用手动整理:
修改setup.py如下:
import os import shutil import subprocess from setuptools import setup, Extension from setuptools.command.build_ext import build_ext class CustomBuildExt(build_ext): def run(self): # 先执行默认的扩展编译流程 super().run() # 获取编译好的扩展文件路径 ext_full_path = self.get_ext_fullpath(self.extensions[0].name) # 用ldd命令找出扩展依赖的共享库(Linux下) ldd_output = subprocess.run( ["ldd", ext_full_path], capture_output=True, text=True ).stdout # 遍历依赖,过滤掉系统库,复制第三方库到包内 for line in ldd_output.splitlines(): if "=>" in line and not line.startswith("\tlinux-vdso.so"): parts = line.split("=>") lib_path = parts[1].strip().split(" ")[0] # 判断是否为第三方库(排除系统默认路径的库) if not lib_path.startswith(("/usr/lib", "/lib", "/usr/local/lib")): dest_dir = os.path.join(os.path.dirname(ext_full_path), "libs") os.makedirs(dest_dir, exist_ok=True) shutil.copy2(lib_path, dest_dir) setup( name="my_package", version="0.1.0", ext_modules=[Extension("my_package.my_extension", sources=["src/my_extension.cpp"])], cmdclass={"build_ext": CustomBuildExt}, package_data={ "my_package": ["libs/*.so"], }, )
方法三:用专用工具处理(跨平台推荐)
不同平台有专门的工具帮你自动处理共享库打包,省心又规范:
- Linux:用
auditwheel,它会自动把依赖的.so嵌入wheel,还能生成兼容多Linux发行版的manywheel包- 安装工具:
pip install auditwheel - 先构建基础wheel:
python3 setup.py bdist_wheel - 修复wheel并包含依赖:
auditwheel repair dist/your_package-*.whl
- 安装工具:
- macOS:用
delocate-wheel,功能类似auditwheel,处理macOS的.dylib依赖 - Windows:可以用
wheeltools或者手动复制.dll(对应Linux的.so)到包目录,再配置package_data
注意:打包完成后,要确保Python能找到这些共享库。可以在包的__init__.py里做路径配置:
import os import ctypes lib_dir = os.path.join(os.path.dirname(__file__), "libs") # 方式1:直接加载指定路径的库 ctypes.CDLL(os.path.join(lib_dir, "thirdparty.so")) # 方式2:设置环境变量,让系统能找到库 os.environ["LD_LIBRARY_PATH"] = f"{lib_dir}:{os.environ.get('LD_LIBRARY_PATH', '')}" # 然后导入你的扩展 from .my_extension import *
内容的提问来源于stack exchange,提问作者Frank C.
相关产品推荐
相关产品推荐

