pyarmor.cli 9.2.6__4-py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyarmor/cli/plugin.py ADDED
@@ -0,0 +1,292 @@
1
+ #! /usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ #############################################################
5
+ # #
6
+ # Copyright @ 2023 - Dashingsoft corp. #
7
+ # All rights reserved. #
8
+ # #
9
+ # Pyarmor #
10
+ # #
11
+ # Version: 8.0.1 - #
12
+ # #
13
+ #############################################################
14
+ #
15
+ #
16
+ # @File: cli/plugin.py
17
+ #
18
+ # @Author: Jondy Zhao (pyarmor@163.com)
19
+ #
20
+ # @Create Date: Fri Apr 21 18:42:14 CST 2023
21
+ #
22
+ import os
23
+
24
+ from importlib.util import spec_from_file_location, module_from_spec
25
+
26
+ from . import logger
27
+
28
+
29
+ __all__ = ['CodesignPlugin', 'MultiPythonPlugin', 'PlatformTagPlugin',
30
+ 'PycPlugin', 'DarwinUniversalPlugin']
31
+
32
+
33
+ class Plugin(object):
34
+
35
+ def __init__(self, ctx=None):
36
+ self.ctx = ctx
37
+
38
+ @staticmethod
39
+ def install(ctx, pkg='pyarmor.cli.plugin'):
40
+ ctx.Plugin = Plugin
41
+
42
+ for pname in ctx.cfg['builder'].get('plugins', '').split():
43
+ if pname in __all__:
44
+ logger.debug('install plugin: %s', pname)
45
+ ctx.plugins.append(globals().get(pname))
46
+ continue
47
+
48
+ for x in '', ctx.local_path, ctx.global_path:
49
+ path = os.path.join(x, pname + '.py')
50
+ if os.path.exists(path):
51
+ logger.debug('plugin script "%s"', path)
52
+ break
53
+ else:
54
+ logger.warning('no plugin "%s" found', pname)
55
+ continue
56
+
57
+ spec = spec_from_file_location(pkg + '.' + pname, path)
58
+ module = module_from_spec(spec)
59
+ spec.loader.exec_module(module)
60
+
61
+ names = getattr(module, '__all__', [])
62
+ logger.debug('install plugins: %s', ' '.join(names))
63
+ ctx.plugins.extend([getattr(module, x, None) for x in names])
64
+
65
+ @staticmethod
66
+ def post_build(ctx, pack=None):
67
+ inputs = ctx.input_paths
68
+ outputs = ctx.outputs
69
+ for plugin in [x for x in ctx.plugins if hasattr(x, 'post_build')]:
70
+ logger.debug('call post build plugin %s', plugin)
71
+ plugin.post_build(ctx, inputs, outputs, pack=pack)
72
+
73
+ @staticmethod
74
+ def post_key(ctx, keyfile):
75
+ kwargs = {
76
+ 'expired': ctx.runtime_expired,
77
+ 'devices': ctx.runtime_devices,
78
+ 'period': ctx.runtime_period,
79
+ 'data': ctx.cmd_options.get('user_data')
80
+ }
81
+ for plugin in [x for x in ctx.plugins if hasattr(x, 'post_key')]:
82
+ logger.debug('call post key plugin %s', plugin)
83
+ plugin.post_key(ctx, keyfile, **kwargs)
84
+
85
+ @staticmethod
86
+ def post_runtime(ctx, source, dest, platform):
87
+ for plugin in [x for x in ctx.plugins if hasattr(x, 'post_runtime')]:
88
+ logger.debug('call post runtime plugin %s', plugin)
89
+ plugin.post_runtime(ctx, source, dest, platform)
90
+
91
+ @staticmethod
92
+ def post_bcc(ctx, res, csource):
93
+ for plugin in [x for x in ctx.plugins if hasattr(x, 'post_bcc')]:
94
+ patched_csource = plugin.post_bcc(ctx, res, csource)
95
+ if patched_csource:
96
+ csource = patched_csource
97
+ return csource
98
+
99
+
100
+ class PycPlugin:
101
+ '''Change all obfuscated scripts name entension from ".pyc" to ".py"'''
102
+
103
+ @staticmethod
104
+ def post_build(ctx, inputs, outputs, pack):
105
+ for path, dirnames, filenames in os.walk(outputs[0]):
106
+ for x in filenames:
107
+ if x.endswith('.pyc'):
108
+ pycname = os.path.join(path, x)
109
+ os.replace(pycname, pycname[:-1])
110
+
111
+
112
+ def osx_sign_binary(dest, identity=None):
113
+ from subprocess import check_call, CalledProcessError, DEVNULL
114
+ cmdlist = ['codesign', '-f', '-s', identity,
115
+ '--all-architectures', '--timestamp', dest]
116
+ logger.debug('%s', ' '.join(cmdlist))
117
+ try:
118
+ check_call(cmdlist, stdout=DEVNULL, stderr=DEVNULL)
119
+ except CalledProcessError as e:
120
+ logger.warning('codesign command failed with error code %d',
121
+ e.returncode)
122
+ except Exception as e:
123
+ logger.warning('codesign command failed with:\n%s', e)
124
+
125
+
126
+ class CodesignPlugin:
127
+ '''codesign darwin runtime extension "pyarmor_runtime"'''
128
+
129
+ @staticmethod
130
+ def post_runtime(ctx, source, dest, platform):
131
+ if platform.startswith('darwin'):
132
+ identity = ctx.cfg['pack'].get('codesign_identify', '-')
133
+ osx_sign_binary(dest, identity)
134
+
135
+
136
+ class PlatformTagPlugin:
137
+ '''Rename runtime extension "pyarmor_runtime" with platform tag.'''
138
+
139
+ @staticmethod
140
+ def post_runtime(ctx, source, dest, platform):
141
+ pyver = '%s%s' % ctx.python_version[:2]
142
+ if platform.startswith('windows.'):
143
+ tag = 'cp%s' % pyver
144
+ tagname = '.'.join(['pyarmor_runtime', tag, 'pyd'])
145
+ logger.info('rename "%s" to "%s"', dest, tagname)
146
+ os.rename(dest, dest.replace('pyarmor_runtime.pyd', tagname))
147
+ elif platform.startswith('darwin.'):
148
+ tag = 'cpython-%s-darwin' % pyver
149
+ tagname = '.'.join(['pyarmor_runtime', tag, 'so'])
150
+ logger.info('rename "%s" to "%s"', dest, tagname)
151
+ os.rename(dest, dest.replace('pyarmor_runtime.so', tagname))
152
+ elif platform.startswith('linux.'):
153
+ arch = platform.split('.')[1]
154
+ tag = 'cpython-%s-%s-linux-gnu' % (pyver, arch)
155
+ tagname = '.'.join(['pyarmor_runtime', tag, 'so'])
156
+ logger.info('rename "%s" to "%s"', dest, tagname)
157
+ os.rename(dest, dest.replace('pyarmor_runtime.so', tagname))
158
+ else:
159
+ raise RuntimeError('PlatformTagPlugin unknown "%s"' % platform)
160
+
161
+
162
+ class MultiPythonPlugin:
163
+ '''Refine runtime package to support multiple python versions'''
164
+
165
+ RUNTIME_PATH = None
166
+ RUNTIME_FILES = []
167
+
168
+ @staticmethod
169
+ def post_runtime(ctx, source, dest, platform):
170
+ MultiPythonPlugin.RUNTIME_PATH = os.path.dirname(dest)
171
+ MultiPythonPlugin.RUNTIME_FILES.append(dest)
172
+
173
+ @staticmethod
174
+ def post_build(ctx, inputs, outputs, pack):
175
+ '''Rewrite runtime package __init__.py'''
176
+ from shutil import move, rmtree
177
+ pyver = 'py%s%s' % ctx.python_version[:2]
178
+ platforms = ctx.target_platforms
179
+
180
+ oneplat = len(platforms) == 1
181
+ pkgpath = MultiPythonPlugin.RUNTIME_PATH if oneplat else \
182
+ os.path.dirname(MultiPythonPlugin.RUNTIME_PATH)
183
+ verpath = os.path.join(pkgpath, pyver)
184
+ if os.path.exists(verpath):
185
+ rmtree(verpath)
186
+ os.makedirs(verpath)
187
+
188
+ pkgscript = os.path.join(pkgpath, '__init__.py')
189
+ with open(pkgscript, encoding=ctx.encoding) as f:
190
+ lines = f.readlines()
191
+ start = 1 if lines[0].startswith('#') else 0
192
+
193
+ if oneplat:
194
+ lines[start:] = '\n'.join([
195
+ 'from sys import version_info as py_version',
196
+ '{0} = __import__("py%d%d.pyarmor_runtime" % py_version[:2],'
197
+ ' globals(), locals(), ["{0}"], 1).{0}'.format('__pyarmor__')
198
+ ])
199
+ with open(pkgscript, 'w', encoding=ctx.encoding) as f:
200
+ f.write(''.join(lines))
201
+ for x in MultiPythonPlugin.RUNTIME_FILES:
202
+ move(x, verpath)
203
+ else:
204
+ lines[start:start] = 'from sys import version_info as py_version\n'
205
+ with open(pkgscript, 'w', encoding=ctx.encoding) as f:
206
+ f.write(''.join(lines).replace(
207
+ "join(['_'", "join(['py%d%d' % py_version[:2], '_'"))
208
+ for x in MultiPythonPlugin.RUNTIME_FILES:
209
+ move(os.path.dirname(x), verpath)
210
+
211
+ MultiPythonPlugin.RUNTIME_FILES.clear()
212
+
213
+
214
+ def osx_merge_binary(target, rtpath, plats):
215
+ from subprocess import check_call, CalledProcessError, DEVNULL
216
+ cmdlist = ['lipo', '-create', '-output', target]
217
+ for plat in plats:
218
+ filename = os.path.join(rtpath, plat, 'pyarmor_runtime.so')
219
+ arch = 'x86_64' if plat == 'darwin_x86_64' else 'arm64'
220
+ cmdlist.extend(['-arch', arch, filename])
221
+ try:
222
+ check_call(cmdlist, stdout=DEVNULL, stderr=DEVNULL)
223
+ return True
224
+ except CalledProcessError as e:
225
+ logger.warning('lipo command "%s" failed with error code %d',
226
+ ' '.join(cmdlist), e.returncode)
227
+ except Exception as e:
228
+ logger.warning('lipo command "%s" failed with:\n%s',
229
+ ' '.join(cmdlist), e)
230
+
231
+
232
+ def find_runtime_package(ctx, output):
233
+ prefix = ctx.import_prefix
234
+ rtname = ctx.runtime_package_name
235
+ if not prefix:
236
+ return os.path.join(output, rtname)
237
+ if isinstance(prefix, str):
238
+ return os.path.join(output, prefix.replace('.', os.path.sep), rtname)
239
+ with os.scandir(output) as iterator:
240
+ for entry in iterator:
241
+ if entry.is_dir():
242
+ if rtname in os.listdir(entry.path):
243
+ return os.path.join(entry.path, rtname)
244
+
245
+
246
+ class DarwinUniversalPlugin:
247
+
248
+ @staticmethod
249
+ def post_build(ctx, inputs, outputs, pack):
250
+ from shutil import rmtree
251
+
252
+ if not ctx.native_platform.startswith('darwin.'):
253
+ return
254
+
255
+ def rebuild_init(oneplat, init_script):
256
+ with open(init_script, 'r', encoding=ctx.encoding) as f:
257
+ lines = f.readlines()
258
+ if oneplat:
259
+ lines[1:] = ['from .pyarmor_runtime import __pyarmor__']
260
+ else:
261
+ for i in range(1, len(lines)):
262
+ if lines[i].strip().startswith("# mach = 'universal'"):
263
+ lines[i] = lines[i].replace('# ', '')
264
+ break
265
+ with open(init_script, 'w', encoding=ctx.encoding) as f:
266
+ f.write(''.join(lines))
267
+
268
+ rtpath = find_runtime_package(ctx, outputs[0])
269
+ if rtpath is None or not os.path.exists(rtpath):
270
+ logger.debug('no found runtime package "%s"', rtpath)
271
+ return
272
+ dirs = [x.name for x in os.scandir(rtpath) if x.is_dir()]
273
+ plats = set(['darwin_x86_64', 'darwin_arm64', 'darwin_aarch64'])
274
+ plats = plats.intersection(set(dirs))
275
+ if len(plats) > 1:
276
+ oneplat = all([x.startswith('darwin_') for x in dirs])
277
+ if oneplat:
278
+ target = rtpath
279
+ else:
280
+ target = os.path.join(rtpath, 'darwin_universal')
281
+ os.makedirs(target, exist_ok=True)
282
+ target = os.path.join(target, 'pyarmor_runtime.so')
283
+
284
+ if not osx_merge_binary(target, rtpath, plats):
285
+ return
286
+
287
+ rebuild_init(oneplat, os.path.join(rtpath, '__init__.py'))
288
+ identity = ctx.cfg['pack'].get('codesign_identify', '-')
289
+ osx_sign_binary(target, identity)
290
+
291
+ # Clean old files
292
+ [rmtree(os.path.join(rtpath, x)) for x in plats]