用 Go 编写 Python 扩展

Posted on Aug 27, 2022

tzfpy 是 tzf 的 Python binding。 如果只是本地可用,Go 代码加上 CGO 扩展编译成 .so 文件就能用了。 不过要做成发布到 PyPI 上在其他地方能直接安装的 wheel 是有些曲折的,看 CI 失败的记录就挺明显的。

有挺多社区文档里没有注明的事情, 解决方案分散在各个 issues 里,当最终成功了以后已经不记得增加的改动的目的是什么。

项目没有用 Poerty 等 fancy 的工具打包,而是用了淳朴的 setup.py(好处是可以直接被 GitHub 识别出来)。

配置 setup.cfg

忘记具体的原因了,大致是为了覆盖某个工具的默认配置,如果没有会构建失败

[install]
install_lib=

配置发布的时候携带 .so 文件

用下来发现要配置两个地方才行

setup.py#L86-90

package_data={"so": ["tzfpy/tzf.so"]},
include_package_data=True,
cmdclass={"bdist_wheel": bdist_wheel, "build_ext": build_ext},
distclass=BinaryDistribution,

MANIFEST.in

include tzfpy/tzf.so

用 cibuildwheel 修正 Linux wheels

直接构建出来的 wheel 不满足 PyPI 的要求,还需要用 pypa/cibuildwheel 修正一遍。

看情况配置

deploy.yml#L102-L108

- name: Build wheels
  uses: pypa/cibuildwheel@v2.8.1
  env:
    CIBW_BUILD_FRONTEND: "build"
    CIBW_MANYLINUX_X86_64_IMAGE: "manylinux2014"
    CIBW_MANYLINUX_I686_IMAGE: "manylinux2014"
    CIBW_BUILD: "*manylinux_x86_64"

End

一路折腾下来感觉干的好像是个挺另类的事情,社区的扩展大多是 C/C++/Rust 实现的,我这种弄个 Go 的在 PyPI 上还没见到过正经的项目。

有个项目叫 asottile/setuptools-golang, 但是作者自己在 #106 说:

to be honest, I haven’t used this for anything more than basic things – but anything in the C api should work

目前的场景还算简单,以后可能会用 https://github.com/go-python/cpy3 写一些更奇怪的扩展搞事情。

不过学会 Rust 用 PyO3 可能是更清爽的做法。