起因
python本身只能做混淆,不能加密,多年的商业软件开发导致有某种“洁癖”:欲将py编译打包
尝试
pyinstaller原理是freeze打包pyc文件,利用工具可完美逆行出源码各种混淆脚本,版本兼容很差,配置繁琐cython 常规使用只能编译单个特殊模块
解决
反复尝试摸索后,还是利用了cython和distutils库,自动化识别并转换py到c源码并编译,放出源码供大家参考
"""
利用cython和distutils编译py到pyd[so] 注意安装cython及本地平台对应编译器
http://flywuya.cnblogs.com/
"""
import os
import shutil
from distutils.core import setup
from distutils.command.build_ext import build_ext
from Cython.Build import cythonize
BUILD_CONFIG = {
'SupportExt': ['.py', '.pyx'],
'CopyOnlyFile': ['__main__.py', '__init__.py'],
'CopyOnlyDir': ['assets'],
'IgnoreDir': ['dist', 'build', '__pycache__'],
}
def copy_tree(src, dst):
""" not like shutil.copytree, dst can be exists """
assert os.path.exists(src)
assert os.path.isdir(src)
os.makedirs(dst, exist_ok=True)
for fn in os.listdir(src):
s = os.path.join(src, fn)
t = os.path.join(dst, fn)
if os.path.isfile(s):
shutil.copy2(s, t)
elif os.path.isdir(s):
copy_tree(s, t)
def build_module(source_file, dst_dir, tmp_dir):
""" cythonize && build ext """
assert os.path.isfile(source_file)
assert not os.path.isabs(source_file)
assert os.path.exists(dst_dir)
os.makedirs(tmp_dir, exist_ok=True)
build_cython = os.path.join(tmp_dir, 'build.cython')
build_temp = os.path.join(tmp_dir, 'build.temp')
build_lib = dst_dir
ext_modules = cythonize(
source_file,
build_dir=build_cython,
language_level=3,
)
class build_here(build_ext):
def initialize_options(self):
super().initialize_options()
self.build_temp = build_temp
self.build_lib = build_lib
setup(
ext_modules=ext_modules,
script_args=['build_ext'],
cmdclass=dict(build_ext=build_here)
)
def build_modules(source_dir, dst_dir, tmp_dir):
""" scan && build modules in source_dir """
assert os.path.exists(source_dir)
assert not os.path.isabs(source_dir)
assert not os.path.isabs(dst_dir)
os.makedirs(dst_dir, exist_ok=True)
for root, dirs, files in os.walk(source_dir):
rel_pth = root[len(source_dir)+1:]
for ignore in BUILD_CONFIG['IgnoreDir']:
if ignore in dirs:
dirs.remove(ignore)
for dn in dirs:
if dn in BUILD_CONFIG['CopyOnlyDir']:
copy_tree(
os.path.join(root, dn),
os.path.join(dst_dir, rel_pth, dn)
)
dirs.remove(dn)
for fn in files:
_, ext = os.path.splitext(fn)
os.makedirs(
os.path.join(dst_dir, rel_pth),
exist_ok=True
)
if fn in BUILD_CONFIG['CopyOnlyFile']:
shutil.copy2(
os.path.join(root, fn),
os.path.join(dst_dir, rel_pth, fn)
)
elif ext.lower() in BUILD_CONFIG['SupportExt']:
build_module(
os.path.join(root, fn),
dst_dir,
os.path.join(tmp_dir, rel_pth),
)
else:
shutil.copy2(
os.path.join(root, fn),
os.path.join(dst_dir, rel_pth, fn)
)
if __name__ == "__main__":
# 这里填写要编译的目录
tasks = [
'app',
]
others = [
'requirements.txt',
'packages',
]
BUILD_CONFIG['CopyOnlyFile'].extend(['settings.py'])
for task in tasks:
build_modules(
task,
os.path.join('dist', task),
os.path.join('build', task),
)
for other in others:
if os.path.isfile(other):
bn = os.path.basename(other)
shutil.copy2(other, os.path.join('dist', bn))
elif os.path.isdir(other):
bn = os.path.basename(other)
copy_tree(other, os.path.join('dist', bn))