2.4. Building obfuscated wheel
The test-project hierarchy is as follows:
$ tree test-project
test-project
├── MANIFEST.in
├── pyproject.toml
├── setup.cfg
└── src
└── parent
├── child
│ └── __init__.py
└── __init__.py
4 directories, 5 files
The content of MANIFEST.in
is:
recursive-include dist/parent/pyarmor_runtime_00xxxx *.so
The content of pyproject.toml
is:
[build-system]
requires = [
"setuptools>=66.1.1",
"wheel"
]
build-backend = "setuptools.build_meta"
The content of setup.cfg
is:
[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__.py
and src/parent/child/__init__.py
are the same:
VERSION = '0.0.1'
First obfuscate the package:
$ cd test-project
$ pyarmor gen --recursive -i src/parent
After successful execution the output is the following directory:
$ tree dist
dist
└── parent
├── child
│ ├── __init__.py
│ └── __pycache__
│ └── __init__.cpython-311.pyc
├── __init__.py
└── pyarmor_runtime_00xxxx
├── __init__.py
└── pyarmor_runtime.so
Next, build the wheel package:
$ python -m build --skip-dependency-check --no-isolation
Unfortunately it raises exception:
* 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)
From traceback we found it uses StaticModule
, then check the source /usr/lib/python3/dist-packages/setuptools/config/expand.py
at line 191 to find class StaticModule
definition. By the source code we know it uses ast.parse
to parse source code directly to get locals. It’s impossible for obfuscated scripts, in order to fix this problem, we need insert a line in the dist/parent/child/__init__.py
like this:
from pyarmor_runtime_00xxxx import __pyarmor__
VERSION = '0.0.1'
...
But pyarmor doesn’t allow to change obfuscated scripts by default, it need disable this restriction by this command:
$ pyarmor cfg -p parent.child.__init__ restrict_module = 0
$ pyarmor gen --recursive -i src/parent
The option pyarmor cfg -p
parent.child.__init__
lets pyarmor disable this restriction only for parent/child/__init__.py
.
Now patch dist/parent/child/__init__.py
and rebuild wheel:
$ python -m build --skip-dependency-check --no-isolation
Rename runtime package and store it in sub-package
If you would rather to rename runtime package to libruntime
and store it in the sub-package parent.child
, you need change the content of MANIFEST.in
to:
recursive-include dist/parent/child/libruntime *.so
and change the content of setup.cfg
to:
[options]
...
packages =
parent
parent.child
parent.child.libruntime
...
And obfuscate the scripts by these configurations:
$ pyarmor cfg package_name_format "libruntime"
$ pyarmor gen --recursive --prefix parent.child src/parent
Don’t forget to patch dist/parent/child/__init__.py
, then build wheel:
$ python -m build --skip-dependency-check --no-isolation
Further more
In order to patch dist/parent/child/__init__.py
automatically, you can write a plugin script .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'")
And enable this plugin:
$ pyarmor cfg plugins + "myplugin"
After that, each build only run the following commands:
$ pyarmor gen --recursive --prefix parent.child src/parent
$ python -m build --skip-dependency-check --no-isolation