2.4. 打包加密脚本成为 Wheel

测试项目的目录结构如下:

$ tree test-project

test-project
├── MANIFEST.in
├── pyproject.toml
├── setup.cfg
└── src
    └── parent
        ├── child
        │   └── __init__.py
        └── __init__.py

文件 MANIFEST.in 的内容如下:

recursive-include dist/parent/pyarmor_runtime_00xxxx *.so

文件 pyproject.toml

[build-system]
    requires = [
        "setuptools>=66.1.1",
        "wheel"
    ]
    build-backend = "setuptools.build_meta"

文件 setup.cfg

[metadata]
    name = parent.child
    version = attr: parent.child.VERSION

[options]
    package_dir =
        =dist/

    packages =
        parent
        parent.child
        parent.pyarmor_runtime_00xxxx

    include_package_data = True

src/parent/__init__.pysrc/parent/child/__init__.py 是相同的:

VERSION = '0.0.1'

首先加密包:

$ cd test-project
$ pyarmor gen --recursive -i src/parent

加密成功之后输出的目录结构如下:

$ tree dist

dist
└── parent
    ├── child
    │   ├── __init__.py
    │   └── __pycache__
    │       └── __init__.cpython-311.pyc
    ├── __init__.py
    └── pyarmor_runtime_00xxxx
        ├── __init__.py
        └── pyarmor_runtime.so

接着就构建 wheel:

$ python -m build --skip-dependency-check --no-isolation

但是却报错了

* Building sdist...
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/setuptools/config/expand.py", line 81, in __getattr__
    return next(
           ^^^^^
StopIteration

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/setuptools/config/expand.py", line 191, in read_attr
    return getattr(StaticModule(module_name, spec), attr_name)

检查错误堆栈发现问题出现在 StaticModule ,通过异常中文件名称和行号,查看在包 setuptools 源代码中这个类的定义,它使用了 ast.parse 直接解析源代码获取模块的局部变量,这当然在加密脚本中行不通的。为了解决这个问题,可以直接在加密后的脚本 dist/parent/child/__init__.py 中增加一行

from pyarmor_runtime_00xxxx import __pyarmor__
VERSION = '0.0.1'
...

但是默认情况下加密脚本是不允许修改的,为了能够修改这个脚本,需要使用下面的命令进行配置:

$ pyarmor cfg -p parent.child.__init__ restrict_module = 0
$ pyarmor gen --recursive -i src/parent

其中选项 pyarmor cfg -p parent.child.__init__ 是只取消对单个模块的 parent/child/__init__.py 的限制,而其他模块依旧使用约束模式。

现在修改 dist/parent/child/__init__.py 之后再次运行下面的命令进行打包:

$ python -m build --skip-dependency-check --no-isolation

自定义运行辅助包的名称

如果你想自定义运行辅助包的名称为 libruntime ,并且存放到子目录 parent.child 下面,你需要修改 MANIFEST.in:

recursive-include dist/parent/child/libruntime *.so

setup.cfg:

[options]
    ...
    packages =
        parent
        parent.child
        parent.child.libruntime
    ...

然后使用下面的配置进行加密:

$ pyarmor cfg package_name_format "libruntime"
$ pyarmor gen --recursive --prefix parent.child src/parent

在创建 wheel 之前不要忘记对 dist/parent/child/__init__.py 进行修改:

$ python -m build --skip-dependency-check --no-isolation

更进一步

为了能够在加密完成之后自动修改 dist/parent/child/__init__.py ,你可以创建一个 加密插件 .pyarmor/myplugin.py:

__all__ = ['VersionPlugin']

class VersionPlugin:

    @staticmethod
    def post_build(ctx, inputs, outputs, pack):
        script = os.path.join(outputs[0], 'parent', 'child', '__init__.py')
        with open(script, 'a') as f:
            f.write("\nVERSION = '0.0.1'")

然后启用这个插件:

$ pyarmor cfg plugins + "myplugin"

这样以后每次打包只需要运行下面的命令就可以了:

$ pyarmor gen --recursive --prefix parent.child src/parent
$ python -m build --skip-dependency-check --no-isolation