如何构造一个兼容好的python打包程序

失败的尝试

pyinstaller的兼容性

其优点是能运行,就能打包成功。兼容性依赖于打包环境

举例说明:

比如docker-compose这个命令,早期使用python编写(现已经用golang重写)

用pyinstaller打包,在一些低版本的linux发行版中出现很多 GLIBC_xxxx not found 的问题 issue链接

其兼容性依赖于GLIBC的版本,但GLIBC通常大家都不敢升级

pyoxidizer的兼容性

pyoxidizer尝试通过编译的方式解决兼容性问题

它的原理如下:

  1. 其编译了静态库https://github.com/indygreg/python-build-standalone
  2. 可以通过rust的工具链x86_64-unknown-linux-musl来静态链接
  3. 打包python代理,各类资源

这样就没有任何依赖了。

参考链接https://gregoryszorc.com/docs/pyoxidizer/0.24.0/pyoxidizer_packaging_static_linking.html

但也有很大缺点,甚至几乎无法使用,如果应用包含C实现python模块就会失败

原因是

  1. 静态程序是无法加载动态库(.so文件)
  2. 即使能加载,这些动态库也可能依赖GLIBC导致兼容性问题
  3. 要解决1,2只能把这些动态库,也静态链接进来
    这就意味着所有C实现python模块都需要重新编译,工程量巨大

GLIBC的兼容性

根据pyoxidizer的文档(链接)

Nearly every binary built on Linux will require linking against libc and will require a symbol provided by glibc. glibc versions it symbols. And when the linker resolves those symbols at link time, it usually uses the version of glibc being linked against. For example, if you link on a machine with glibc 2.19, the symbol versions in the produced binary will be against version 2.19 and the binary will load against glibc versions >=2.19. But if you link on a machine with glibc 2.29, symbol versions are against version 2.29 and you can only load against versions >= 2.29.

This means that to ensure maximum portability, you want to link against old glibc symbol versions. While it is possible to use old symbol versions when a more modern glibc is present, the path of least resistance is to build in an environment that has an older glibc.

其推荐使用debian 8作为基础环境,可以看到debian的GLIBC版本为2.19

1
2
3
4
5
6
root@3174dcfb96c4:~# ldd --version
ldd (Debian GLIBC 2.19-18+deb8u10) 2.19
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.

根据 glibc wiki,2.19为2014-02-07发布,非常古老

编译Python

解决apt源问题

1
2
echo "deb http://deb.freexian.com/extended-lts jessie main contrib non-free" > /etc/apt/sources.list
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A07310D369055D5A

安装编译环境和依赖库

1
2
apt update
apt install -y build-essential pkg-config libbz2-dev libffi-dev libgdbm-dev liblzma-dev libncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev lzma lzma-dev tk-dev uuid-dev zlib1g-dev

编译openssl

Python3.9依赖openssl 1.1.1

1
2
3
4
tar -xzvf openssl-1.1.1v.tar.gz
cd openssl-1.1.1v
./config && make && make install
ldconfig /usr/local/lib

编译python

1
2
3
4
tar -xzvf Python-3.9.18.tgz
cd Python-3.9.18
./configure --enable-shared && make && make install
ldconfig /usr/local/lib

安装pyinstaller

1
pip3 install pyinstaller

构建镜像

打包依赖的

1
2
3
cd /usr
cp /usr/bin/objdump /usr/local/bin
tar -czvf /tmp/local.tgz local

通过Dockerfile构建镜像

1
2
3
FROM debian:8
ADD local.tgz /usr
RUN ldconfig /usr/local/lib

此时我就成功构建一个兼容性较好的pyinstaller的打包环境