Python 源码保护的自动化构建方案
2020 5 9 06:20 PM 651次查看
使用的方法也很简单,先编写一个
setup.py
文件:from distutils.core import setup
import glob
from Cython.Build import cythonize
py_files = glob.glob('*/**/*.py', recursive=True) # 第一层的 py 文件不编译,因为 so 文件不支持直接用 python -m 来执行
setup(
ext_modules=cythonize(
py_files,
nthreads=4 # 同时用 4 个进程来编译 py 到 c,根据自己的 CPU 核数来设置
)
)
需要的话,可以参考文档来添加 compiler_directives
。然后执行
python setup.py build_ext --inplace -j 4
就可以开始编译了,其中 --inplace
是让编译生成的 c 和 so 文件与 py 文件放在一起,-j 4
是同时使用 4 个进程来编译 c 到 so。编译完成后,删掉其中的 py 和 c 文件,就可以发布了。执行时可以和普通的 Python 项目一样,使用
python -m app
之类的方式来运行即可。如果第一层的
app.py
文件也很敏感,可以把它放在下层的包里,app.py
里只写一句 import xxx.app
即可。这些过程手动处理起来比较麻烦,可以用 GitLab CI 来自动构建,几行代码即可搞定。但是随着项目的增长,又出现了新的问题:编译时间越来越长,甚至长达半小时之久。
于是我又研究了一下 Cython 的编译缓存,发现它会检查需要编译的 py 文件是否已经生成了对应的 so 文件;如果有且这个文件的修改时间比 py 文件更晚,它就会跳过编译这个 py 文件。而我在用 docker 来编译的时候,只
COPY
了源代码,并没有带上 so,自然就需要重新编译了。可是如果直接把 so 文件也
COPY
过去,它的修改时间又比 py 文件晚了,即使改了代码也不会被重新编译,这显然也不行。要解决这个矛盾的话,我想到了挂载一个 volume,把源码和 so 文件都保存在宿主机上,这样文件的修改时间就不会丢失了。
而在编译前还需要做些同步的准备工作,即比较需要编译的新源码文件夹和 volume 里的上次编译的文件夹,删除已经不存在的 py、c 和 so 文件,复制新增或修改过的 py 文件。这样新的 py 文件修改时间会比 so 更晚,会被重新编译;而其他 py 文件则会跳过编译。
这个解决方案看上去很完美,但是
docker build
并不支持挂载 volume,真是让人崩溃。无奈之下我只能用
docker run
来挂载 volume。编译之后,再把 volume 里需要的文件(即第一层的 py 文件和后续层的 so 文件)复制到镜像里,然后用 docker commit
来保存新的镜像。注意:如果是本地用 docker 来构建的话,是可以在 Dockerfile
里使用 COPY
或 ADD
的;但 GitLab CI 用的是 docker in docker,无法直接访问宿主机里的路径。为此还需要写 2 个
Dockerfile
,一个用来编译,一个用来打包:build:
script:
- docker build -f Dockerfile_compile -t xxx-compile .
- docker rm xxx_compile || true
- docker run --name xxx_compile -v $CACHE_DIR:/root/xxx xxx-compile python /root/setup.py build_ext --inplace -j 4
- docker commit xxx_compile xxx-compile
- docker rm xxx_compile || true
- docker build -f Dockerfile_package -t $IMAGE_NAME .
Dockerfile_package
所做的是从 xxx-compile
里复制 /usr/local/lib
和编译生成的文件到新的镜像,这样可以减少镜像的大小,避免保存中间层。测试一下发现编译时间从半小时缩短到半分钟,搞定!
做到这里我又想到了新的方案,实际上不需要保存 py 和 so 的修改时间,只要保证不想编译的 py 文件有个更晚的 so 文件即可。
于是按最初的方案直接构建,保存成中间镜像。在第二步构建时,把中间镜像里的文件
COPY
到另一个文件夹,然后进行一次同步,把不需要编译的 so 文件 cp
到对应的路径。等待编译完成后,也保存成中间镜像。在第三步构建时,再把中间镜像里的文件 COPY
到新镜像即可。这个方案既不需要暴露宿主机的文件夹,也不需要用到
docker commit
这种有代码洁癖的人不愿意使用的语句,完美!
0条评论 你不来一发么↓