python把c扩展模块打包成免编译且可pip install包的方法

python用c语言编写的扩展模块,在setup.py sdist或者bdist打包时,默认是把c源码编译进去,在pip install的时候先编译再安装的,即使我把so加到了MANIFEST.in文件中,查看编出来的包里包含了so,但是仍然不会进行so的安装。搜索查阅了一下,没有找到相关的文章可以解决这个问题。因此只能自己通过debug研究python-prctl的安装流程,把相关的流程走通了。总结记录一下:

背景

首先说明一下背景:

我们的业务逻辑有一部分需要使用c语言来写,这部分c语言写的代码只能在特定的编译机上编译成.so,我们使用的python版本统一定为python3.8。因此我编写了一个python的c扩展模块,并对底层c接口进行了相应的python函数包装,在使用的时候,需要放到内网pip源,用pip install的方式安装。所有使用人的os版本、python版本都是一致的,且没有编译需要的相关文件。

原理

调试方式

由于时间比较紧,所以没有详细研究pip install的流程,python-prctl这个模块的项目结构和我们的类似,不同的是它是pip install的时候先编译再安装的,因此用它当作例子来学习。

在pycharm上配置ssh远程解释器,然后配置执行方式:

一些简单观察到的流程(没有实际看完整流程,只是靠debug和猜测推断了下)

pip install的流程

css 复制代码
1. egg_info
2. install
    1) build
        a) build_py
        b) build_clib
        c) build_ext
        d) build_scripts
    2) install_lib
    3) install_egg_info
    3) install_scripts

编译的.so,会拷贝到build/lib.linux-x86_64-3.8目录

实现思路

经过多次踩坑(一直不安装so,甚至改install命令自己把so拷贝到site-packages,但是uninstall时又不会卸载等等),最终确定一个实现的思路,虽然不编译,但是在build_ext的流程中,把对应的so拷贝到build/lib.linux-x86_64-3.8目录,让后续的安装流程能够正常安装进行,这样后面的流程能够正常的把so拷贝到site-packages目录,且卸载时正常删除。这样对原流程的改动最小,影响也最少。

build_ext命令的定制

我们的业务工程代码,对于Makefile有整体的封装,怎么搜索头文件,怎么搜索lib库都有封装,我们想要调用这些封装的内容来去找我们的依赖模块,这样我们依赖的模块有修改的话,该模块不用修改。所以不能在setup.py中写死include目录及libraries。因此对build_ext做一些定制。把include目录、链接的.a文件、源码通过Makefile传递进去。

setup.py 复制代码
class my_build_ext(build_ext):
    user_options = build_ext.user_options
    user_options.extend([
        ('extra-objects=', None, "list of extra_objects"),
        ('sources=', None, "list of sources")
    ])

    def initialize_options(self):
        super(my_build_ext, self).initialize_options()
        self.extra_objects = None
        self.sources = None

    def run(self):
        include_dirs = []
        for include_dir in self.include_dirs:
            include_dirs.extend(include_dir.split())
        self.include_dirs = include_dirs

        for extension in self.extensions:
            if self.sources:
                extension.sources = self.sources.split()
                if self.extra_objects:
                    extension.extra_objects = self.extra_objects.split()
        super(my_build_ext, self).run()

setup.py的定制

根据实现思路的描述,这里主要处理pip install的时候的流程,跳过编译阶段,直接把so拷贝到build目录

ruby 复制代码
def build_extensions(self):
    if not self.sources:
        # 没有传sources 说明是pip install, 跳过编译阶段, 直接把包里面的so拷贝到build目录
        for extension in self.extensions:
            # 找编译的目标目录
            ext_path = self.get_ext_fullpath(extension.name)
            ext_name = os.path.basename(ext_path)
            ext_dir = os.path.dirname(ext_path)
            mkpath(ext_dir, 0o777, dry_run=0)
            copy_file(ext_name, ext_dir)
        return
    # build
    super(my_build_ext, self).build_extensions()

makefile编写

在定义make all和make clean

make all时,先调用setup.py build_ext把so编译出来,然后拷贝到当前目录,最后调用setup.py sdist编译为可pip安装的包

makefile 复制代码
all:
   python3.8 setup.py build_ext --include-dirs="include" --sources="_sample_extension_mod.c"
   \cp build/lib.linux-x86_64-3.8/_sample_extension_mod.cpython-38-x86_64-linux-gnu.so .
   python3.8 setup.py sdist
clean:
   rm -rf build dist *.so

最终效果

可以看到最终实现了预期, 安装的时候, 自动把so给安装上了. 卸载的时候, 也顺利把so及相关文件卸载了.

相关推荐
红尘散仙1 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
心中有国也有家1 小时前
GE图引擎深度解析——CANN的计算图优化与执行引擎
人工智能·pytorch·python·学习·numpy
卷毛的技术笔记2 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
编程大师哥2 小时前
匿名函数 lambda + 高阶函数
java·python·算法
vb2008113 小时前
FastAPI APIRouter
开发语言·python
会编程的土豆3 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
adrninistrat0r3 小时前
Java调用链MCP分析工具
java·python·ai编程
喵个咪3 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
杨充3 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
basketball6163 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang