pyarmor 8.5.7__zip → 8.5.9__zip

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.
Files changed (124) hide show
  1. {pyarmor-8.5.7 → pyarmor-8.5.9}/PKG-INFO +1 -1
  2. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/__init__.py +1 -1
  3. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/__main__.py +7 -12
  4. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/bug.py +2 -2
  5. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/context.py +5 -1
  6. pyarmor-8.5.9/pyarmor/cli/core.data.1 +0 -0
  7. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/core.data.2 +0 -0
  8. pyarmor-8.5.9/pyarmor/cli/core.data.3 +0 -0
  9. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/default.cfg +6 -9
  10. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/docker.py +4 -2
  11. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/generate.py +24 -12
  12. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/plugin.py +5 -4
  13. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/repack.py +371 -199
  14. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/resource.py +1 -1
  15. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/config.py +1 -1
  16. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/packer.py +4 -2
  17. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor.egg-info/PKG-INFO +1 -1
  18. pyarmor-8.5.9/pyarmor.egg-info/requires.txt +1 -0
  19. {pyarmor-8.5.7 → pyarmor-8.5.9}/setup.py +1 -1
  20. pyarmor-8.5.7/pyarmor/cli/core.data.1 +0 -0
  21. pyarmor-8.5.7/pyarmor/cli/core.data.3 +0 -0
  22. pyarmor-8.5.7/pyarmor.egg-info/requires.txt +0 -1
  23. {pyarmor-8.5.7 → pyarmor-8.5.9}/MANIFEST.in +0 -0
  24. {pyarmor-8.5.7 → pyarmor-8.5.9}/README.md +0 -0
  25. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/README.rst +0 -0
  26. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/__init__.py +0 -0
  27. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/benchmark.py +0 -0
  28. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/build_meta.py +0 -0
  29. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/bootstrap.py +0 -0
  30. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/config.py +0 -0
  31. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/hdinfo.py +0 -0
  32. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/merge.py +0 -0
  33. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/mixer.py +0 -0
  34. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/public_capsule.zip +0 -0
  35. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/register.py +0 -0
  36. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cli/shell.py +0 -0
  37. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/cobuilder.py +0 -0
  38. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/README-ZH.md +0 -0
  39. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/README.md +0 -0
  40. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/build-for-exe.bat +0 -0
  41. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/build-for-freeze.bat +0 -0
  42. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/build-with-project.bat +0 -0
  43. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/build-with-project.sh +0 -0
  44. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/cx_Freeze/hello.py +0 -0
  45. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/cx_Freeze/queens.py +0 -0
  46. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/cx_Freeze/setup.py +0 -0
  47. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/helloworld/foo.py +0 -0
  48. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/obfuscate-app.bat +0 -0
  49. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/obfuscate-app.sh +0 -0
  50. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/obfuscate-pkg.bat +0 -0
  51. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/obfuscate-pkg.sh +0 -0
  52. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pack-obfuscated-scripts.bat +0 -0
  53. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pack-obfuscated-scripts.sh +0 -0
  54. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/py2exe/hello.py +0 -0
  55. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/py2exe/queens.py +0 -0
  56. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/py2exe/setup.py +0 -0
  57. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Arithmetic.py +0 -0
  58. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Calls.py +0 -0
  59. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/CommandLine.py +0 -0
  60. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Constructs.py +0 -0
  61. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Dict.py +0 -0
  62. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Exceptions.py +0 -0
  63. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Imports.py +0 -0
  64. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Instances.py +0 -0
  65. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Lists.py +0 -0
  66. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Lookups.py +0 -0
  67. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/NewInstances.py +0 -0
  68. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Numbers.py +0 -0
  69. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Setup.py +0 -0
  70. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Strings.py +0 -0
  71. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/Tuples.py +0 -0
  72. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/With.py +0 -0
  73. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/clockres.py +0 -0
  74. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/package/__init__.py +0 -0
  75. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/package/submodule.py +0 -0
  76. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/pybench.py +0 -0
  77. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/pybench/systimes.py +0 -0
  78. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/simple/queens.py +0 -0
  79. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/testmod/hello.py +0 -0
  80. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/testmod/queens.py +0 -0
  81. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/testpkg/main.py +0 -0
  82. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/testpkg/mypkg/__init__.py +0 -0
  83. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/examples/testpkg/mypkg/foo.py +0 -0
  84. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/helper/__init__.py +0 -0
  85. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/helper/build_data_module.py +0 -0
  86. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/helper/buildext.py +0 -0
  87. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/helper/get_bind_key.py +0 -0
  88. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/helper/get_license_info.py +0 -0
  89. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/helper/get_platform_name.py +0 -0
  90. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/helper/merge.py +0 -0
  91. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/helper/repack.py +0 -0
  92. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/helper/superuntime.py +0 -0
  93. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/license.tri +0 -0
  94. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/platforms/darwin/x86_64/_pytransform.dylib +0 -0
  95. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/platforms/linux/x86/_pytransform.so +0 -0
  96. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/platforms/linux/x86_64/_pytransform.so +0 -0
  97. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/platforms/windows/x86/_pytransform.dll +0 -0
  98. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/platforms/windows/x86_64/_pytransform.dll +0 -0
  99. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/plugins/README.md +0 -0
  100. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/plugins/check_ntp_time.py +0 -0
  101. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/polyfills/__init__.py +0 -0
  102. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/polyfills/argparse.py +0 -0
  103. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/product.key +0 -0
  104. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/project.py +0 -0
  105. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/protect_code.pt +0 -0
  106. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/protect_code2.pt +0 -0
  107. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/public.key +0 -0
  108. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/public_capsule.zip +0 -0
  109. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/pyarmor-deprecated.py +0 -0
  110. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/pyarmor-webui.py +0 -0
  111. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/pyarmor.py +0 -0
  112. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/pyimcore.py +0 -0
  113. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/pyshield.key +0 -0
  114. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/pyshield.lic +0 -0
  115. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/pytransform.py +0 -0
  116. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/reform.py +0 -0
  117. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/register.py +0 -0
  118. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/sppmode.py +0 -0
  119. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor/utils.py +0 -0
  120. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor.egg-info/SOURCES.txt +0 -0
  121. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor.egg-info/dependency_links.txt +0 -0
  122. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor.egg-info/entry_points.txt +0 -0
  123. {pyarmor-8.5.7 → pyarmor-8.5.9}/pyarmor.egg-info/top_level.txt +0 -0
  124. {pyarmor-8.5.7 → pyarmor-8.5.9}/setup.cfg +0 -0
@@ -2,24 +2,367 @@ import logging
2
2
  import marshal
3
3
  import os
4
4
  import shutil
5
- import struct
6
5
  import sys
7
- import tempfile
8
6
 
9
7
  from fnmatch import fnmatch
10
- from importlib._bootstrap_external import _code_to_timestamp_pyc
11
- from subprocess import check_call, check_output, DEVNULL
8
+ from subprocess import check_call, DEVNULL
12
9
 
10
+ # Only used by BundleRepacker, they could be removed with deprecated routines
11
+ import struct
12
+ import tempfile
13
+ from importlib._bootstrap_external import _code_to_timestamp_pyc
13
14
  from PyInstaller.archive.writers import ZlibArchiveWriter, CArchiveWriter
14
15
  from PyInstaller.archive.readers import CArchiveReader
15
- try:
16
- from PyInstaller.loader.pyimod02_archive import ZlibArchiveReader
17
- except ModuleNotFoundError:
18
- # Since 5.3
19
- from PyInstaller.loader.pyimod01_archive import ZlibArchiveReader
20
16
  from PyInstaller.compat import is_darwin, is_linux, is_win
21
17
 
22
18
 
19
+ logger = logging.getLogger('repack')
20
+
21
+
22
+ def find_packer(mode):
23
+ if mode in ('onefile', 'onedir', 'F', 'D', 'FC', 'DC'):
24
+ return AutoRepacker
25
+
26
+ if isinstance(mode, str) and mode.endswith('.spec'):
27
+ return SpecRepacker
28
+
29
+ return BundleRepacker
30
+
31
+
32
+ def autoclean_output(output, autoclean=True):
33
+ if os.path.exists(output) and autoclean:
34
+ n = len(os.path.abspath(output).split(os.sep))
35
+ if n < 3:
36
+ prompt = 'Are you sure to remove path "%s" (y/n)? '
37
+ choice = input(prompt % output).lower()[:1]
38
+ if not choice == 'y':
39
+ return
40
+ logger.info('clean output path "%s"', output)
41
+ shutil.rmtree(output)
42
+
43
+
44
+ # This patch is used to modify the specfile generated by PyInstaller
45
+ #
46
+ # It mainly does:
47
+ #
48
+ # 1. Find all the imported modules and packages, save to hook script
49
+ #
50
+ # 2. Find imported modules and packages in the same path of main
51
+ # script, and save their names to one list (all of them will be
52
+ # obfuscated automatically)
53
+ #
54
+ spec_patch_code = '''
55
+ import marshal
56
+ import os
57
+
58
+ from PyInstaller.compat import base_prefix, EXTENSION_SUFFIXES
59
+ from sys import prefix, exec_prefix
60
+ from fnmatch import fnmatch
61
+
62
+ exlist = set([base_prefix, prefix, exec_prefix])
63
+ expats = [os.path.join(os.path.normpath(x), '*') for x in exlist]
64
+
65
+ src = {src!r}
66
+ resfile = {resfile!r}
67
+ hookscript = {hookscript!r}
68
+
69
+ nsrc = len(src) + 1
70
+ rsrc = os.path.relpath(src)
71
+ rsrc = '' if rsrc == '.' else rsrc
72
+ psrc = os.path.join(src, '*')
73
+
74
+ hiddenimports = set([])
75
+ plist = set([])
76
+ mlist = []
77
+
78
+ for name, path, kind in a.pure:
79
+ if name.startswith('pyi_rth'):
80
+ continue
81
+ hiddenimports.add(name)
82
+ if fnmatch(path, psrc) and not any([fnmatch(path, x) for x in expats]):
83
+ if name.find('.') == -1 and os.path.basename(path) != '__init__.py':
84
+ mlist.append(os.path.join(rsrc, path[nsrc:]))
85
+ else:
86
+ pkgname = os.path.dirname(path[nsrc:]).split(os.sep)[0]
87
+ plist.add(os.path.join(rsrc, pkgname))
88
+
89
+ for name, path, kind in a.binaries:
90
+ if kind == 'EXTENSION':
91
+ for x in EXTENSION_SUFFIXES:
92
+ if fnmatch(name, '*' + x):
93
+ name = name[:-len(x)]
94
+ break
95
+ hiddenimports.add(name.replace(os.sep, '.'))
96
+
97
+ with open(resfile, 'wb') as f:
98
+ marshal.dump(mlist + list(plist), f)
99
+ with open(hookscript, 'w') as f:
100
+ f.write("hiddenimports=[%s]" % ", ".join([repr(x) for x in hiddenimports]))
101
+ '''
102
+
103
+
104
+ class AutoRepacker:
105
+ """New repacker to support PyInstaller 6.0+
106
+
107
+ Args:
108
+ ctx: build context
109
+ mode: onefile or onedir
110
+ inputs: main script to pack
111
+ output: path to store final bundle, default is `dist`
112
+ """
113
+
114
+ def __init__(self, ctx, mode, inputs, output):
115
+ self.ctx = ctx
116
+ self.script = inputs[0]
117
+ self.output = os.path.normpath(output) if output else 'dist'
118
+
119
+ self.name = os.path.splitext(os.path.basename(self.script))[0]
120
+ self.obfpath = os.path.normpath(self.ctx.pack_obfpath)
121
+ self.packpath = os.path.normpath(self.ctx.pack_basepath)
122
+ self.workpath = os.path.join(self.packpath, 'build')
123
+ self.pyicmd = [sys.executable, '-m', 'PyInstaller']
124
+ self.autoclean = mode in ('FC', 'DC')
125
+ self.modeopt = '-F' if mode in ('onefile', 'F', 'FC') else '-D'
126
+ self.init_opts()
127
+
128
+ def init_opts(self):
129
+ self.pyiopts = []
130
+
131
+ opts = self.ctx.pyi_options
132
+ exopts = '--noconfirm', '-y', '--onefile', '-F', '--onedir', '-D'
133
+ exvalues = '--distpath', '--specpath', '--workpath'
134
+
135
+ n = 0
136
+ while n < len(opts):
137
+ x = opts[n]
138
+ if x in ('--name', '-n'):
139
+ self.name = opts[n+1]
140
+
141
+ if x in ('--noconfirm', '-y'):
142
+ self.autoclean = True
143
+
144
+ if x in exvalues:
145
+ n += 1
146
+ elif x not in exopts:
147
+ self.pyiopts.append(x)
148
+ n += 1
149
+
150
+ def analysis(self):
151
+ """Got imported modules and packages by PyInstaller
152
+
153
+ Generate hook script
154
+ Return file/dir list need to be obfuscated
155
+ """
156
+ cmdspec = [sys.executable, '-m', 'PyInstaller.utils.cliutils.makespec']
157
+ cmdspec.extend(self.pyiopts)
158
+ cmdspec.append(self.script)
159
+ logger.debug('%s', ' '.join(cmdspec))
160
+ logger.info('call PyInstaller to generate specfile')
161
+ check_call(cmdspec, stdout=DEVNULL, stderr=DEVNULL)
162
+
163
+ specfile = self.name + '.spec'
164
+ rtname = self.ctx.runtime_package_name
165
+ resfile = os.path.join(self.packpath, 'resources.list')
166
+ hookscript = os.path.join(self.packpath, 'hook-%s.py' % rtname)
167
+ self.patch_specfile(specfile, hookscript, resfile)
168
+
169
+ cmdlist = self.pyicmd + ['--clean', '--workpath', self.workpath]
170
+ cmdlist.append(specfile)
171
+ logger.debug('%s', ' '.join(cmdlist))
172
+ logger.info('call PyInstaller to analysis, '
173
+ 'it may take several minutes ...')
174
+ check_call(cmdlist, stdout=DEVNULL, stderr=DEVNULL)
175
+
176
+ with open(resfile, 'rb') as f:
177
+ reslist = marshal.load(f)
178
+
179
+ resoptions = self.ctx.get_res_options('')
180
+ excludes = resoptions.get('excludes', '').split()
181
+ if excludes:
182
+ reslist = [x for x in reslist
183
+ if not any([fnmatch(x, pat) for pat in excludes])]
184
+ return reslist
185
+
186
+ def build(self):
187
+ """Generate final bundle to output"""
188
+ obfscript = os.path.join(self.obfpath, os.path.basename(self.script))
189
+ cmdlist = self.pyicmd + [
190
+ '--clean',
191
+ '--distpath', self.output,
192
+ '--workpath', self.workpath,
193
+ '--additional-hooks-dir', self.packpath,
194
+ self.modeopt
195
+ ]
196
+ cmdlist.extend(self.pyiopts)
197
+ cmdlist.append(obfscript)
198
+
199
+ logger.debug('%s', ' '.join(cmdlist))
200
+ logger.info('call PyInstaller to generate final bundle ...\n')
201
+ check_call(cmdlist)
202
+ logger.info('')
203
+ logger.info('the final bundle has been generated to "%s" successfully',
204
+ self.output)
205
+
206
+ def patch_specfile(self, specfile, hookscript, resfile):
207
+ lines = []
208
+ with open(specfile, 'r', encoding='utf-8') as f:
209
+ for line in f:
210
+ if line.startswith('pyz = PYZ'):
211
+ break
212
+ lines.append(line)
213
+ else:
214
+ logger.error('no found line starts with "pyz = PYZ"')
215
+ raise RuntimeError('unsupported specfile "%s"' % specfile)
216
+
217
+ lines.append(spec_patch_code.format(
218
+ src=os.path.abspath(os.path.dirname(self.script)),
219
+ hookscript=os.path.abspath(hookscript),
220
+ resfile=os.path.abspath(resfile)))
221
+
222
+ with open(specfile, 'w', encoding='utf-8') as f:
223
+ f.write(''.join(lines))
224
+
225
+ def check(self):
226
+ autoclean_output(self.output, self.autoclean)
227
+
228
+
229
+ manual_spec_patch = '''
230
+ # Pyarmor patch start:
231
+
232
+ def apply_pyarmor_patch():
233
+
234
+ srcpath = {srcpath!r}
235
+ obfpath = {obfpath!r}
236
+ pkgname = {rtname!r}
237
+ pkgpath = os.path.join(obfpath, pkgname)
238
+ extpath = os.path.join(pkgname, {extname!r})
239
+
240
+ if hasattr(a.pure, '_code_cache'):
241
+ code_cache = a.pure._code_cache
242
+ else:
243
+ from PyInstaller.config import CONF
244
+ code_cache = CONF['code_cache'].get(id(a.pure))
245
+
246
+ srclist = [os.path.normcase(x) for x in srcpath]
247
+ def match_obfuscated_script(orgpath):
248
+ for x in srclist:
249
+ if os.path.normcase(orgpath).startswith(x):
250
+ return os.path.join(obfpath, orgpath[len(x)+1:])
251
+
252
+ count = 0
253
+ for i in range(len(a.scripts)):
254
+ x = match_obfuscated_script(a.scripts[i][1])
255
+ if x and os.path.exists(x):
256
+ a.scripts[i] = a.scripts[i][0], x, a.scripts[i][2]
257
+ count += 1
258
+ if count == 0:
259
+ raise RuntimeError('No obfuscated script found')
260
+
261
+ for i in range(len(a.pure)):
262
+ x = match_obfuscated_script(a.pure[i][1])
263
+ if x and os.path.exists(x):
264
+ code_cache.pop(a.pure[i][0], None)
265
+ a.pure[i] = a.pure[i][0], x, a.pure[i][2]
266
+
267
+ a.pure.append((pkgname, os.path.join(pkgpath, '__init__.py'), 'PYMODULE'))
268
+ a.binaries.append((extpath, os.path.join(obfpath, extpath), 'EXTENSION'))
269
+
270
+ apply_pyarmor_patch()
271
+
272
+ # Pyarmor patch end.
273
+ '''
274
+
275
+
276
+ class SpecRepacker:
277
+ """Patch specfile and use it to pack obfuscated scripts
278
+
279
+ Args:
280
+ ctx: build context
281
+ specfile: specfile need to be patched
282
+ inputs: main script to pack
283
+ output: path to store final bundle, default is `dist`
284
+ """
285
+
286
+ def __init__(self, ctx, specfile, inputs, output):
287
+ self.ctx = ctx
288
+ self.specfile = specfile
289
+ self.script = inputs[0]
290
+ self.output = os.path.normpath(output) if output else 'dist'
291
+
292
+ self.pyicmd = [sys.executable, '-m', 'PyInstaller']
293
+ self.obfpath = os.path.normpath(self.ctx.pack_obfpath)
294
+
295
+ def build(self):
296
+ """Generate patched specfile and pack obfuscated scripts"""
297
+ specfile = self.specfile[:-5] + '.patched.spec'
298
+ logger.info('generate patched specfile "%s"', specfile)
299
+
300
+ rtpkg = self.ctx.runtime_package_name
301
+ for x in os.listdir(os.path.join(self.obfpath, rtpkg)):
302
+ if x.startswith('pyarmor_runtime'):
303
+ extname = x
304
+ break
305
+ else:
306
+ raise RuntimeError('no found extension `pyarmor_runtime`')
307
+
308
+ patch = manual_spec_patch.format(
309
+ srcpath=[os.path.abspath(os.path.dirname(self.script))],
310
+ obfpath=os.path.abspath(self.obfpath),
311
+ rtname=self.ctx.runtime_package_name,
312
+ extname=extname)
313
+
314
+ with open(self.specfile, 'r', encoding='utf-8') as f:
315
+ lines = f.readlines()
316
+
317
+ n = 0
318
+ for line in lines:
319
+ if line.startswith('pyz = PYZ'):
320
+ lines[n:n] = [patch]
321
+ break
322
+ n += 1
323
+ else:
324
+ logger.error('no found line starts with "pyz = PYZ"')
325
+ raise RuntimeError('unsupported specfile "%s"' % self.specfile)
326
+
327
+ with open(specfile, 'w', encoding='utf-8') as f:
328
+ f.write(''.join(lines))
329
+
330
+ cmdlist = self.pyicmd + [
331
+ '--clean',
332
+ '--distpath', self.output,
333
+ '--workpath', os.path.join(self.ctx.pack_basepath, 'build'),
334
+ specfile
335
+ ]
336
+ logger.info('call PyInstaller to generate final bundle ...'
337
+ '\n\n%s\n', ' '.join(cmdlist))
338
+ check_call(cmdlist)
339
+ logger.info('')
340
+ logger.info('the final bundle has been generated to "%s" successfully',
341
+ self.output)
342
+
343
+ def check(self):
344
+ options = self.ctx.pyi_options
345
+ autoclean = '-y' in options or '--noconfirm' in options
346
+ autoclean_output(self.output, autoclean)
347
+
348
+
349
+ ##############################################################
350
+ #
351
+ # Deprecated routines
352
+ #
353
+ # This is the old method to repack executable generated by PyInstaller
354
+ #
355
+ # Why is it deprecated?
356
+ #
357
+ # 1. It need hack PyInstaller bundle structure which may be changed in
358
+ # different PyInstaller version.
359
+ #
360
+ # Actually, it doesn't work for PyInstaller 5.13+ because of this
361
+ #
362
+ # 2. Not friend to junior users
363
+ #
364
+ ##
365
+
23
366
  # Type codes for PYZ PYZ entries
24
367
  PYZ_ITEM_MODULE = 0
25
368
  PYZ_ITEM_PKG = 1
@@ -42,9 +385,6 @@ PKG_ITEM_SPLASH = 'l' # splash resources
42
385
  EXTRACT_SUFFIX = '_extracted'
43
386
 
44
387
 
45
- logger = logging.getLogger('repack')
46
-
47
-
48
388
  class CArchiveReader2(CArchiveReader):
49
389
 
50
390
  def find_magic_pattern(self, fp, magic_pattern):
@@ -94,6 +434,12 @@ class CArchiveReader2(CArchiveReader):
94
434
  if hasattr(self, 'open_embedded_archive'):
95
435
  return self.open_embedded_archive(name)
96
436
 
437
+ try:
438
+ from PyInstaller.loader.pyimod02_archive import ZlibArchiveReader
439
+ except ModuleNotFoundError:
440
+ # Since 5.3
441
+ from PyInstaller.loader.pyimod01_archive import ZlibArchiveReader
442
+
97
443
  ndx = self.toc.find(name)
98
444
  (dpos, dlen, ulen, flag, typcd, nm) = self.toc.get(ndx)
99
445
  return ZlibArchiveReader(self.path, self.pkg_start + dpos)
@@ -307,13 +653,16 @@ def repack_executable(executable, buildpath, obfpath, rtentry, codesign=None):
307
653
  logger.info('generate patched bundle "%s" successfully', executable)
308
654
 
309
655
 
310
- class Repacker:
656
+ class BundleRepacker:
657
+ """Deprecated method since v8.5.8"""
311
658
 
312
- def __init__(self, executable, buildpath, codesign=None):
659
+ def __init__(self, ctx, executable, inputs, output):
660
+ self.ctx = ctx
313
661
  self.executable = executable
314
- self.buildpath = buildpath
315
- self.codesign = codesign
316
- self.extract_carchive(executable, buildpath)
662
+ self.output = os.path.normpath(output) if output else 'dist'
663
+ self.buildpath = ctx.pack_basepath
664
+ self.codesign = ctx.cfg['pack'].get('codesign_identify', None)
665
+ self.extract_carchive(executable, self.buildpath)
317
666
 
318
667
  def check(self):
319
668
  try:
@@ -361,6 +710,10 @@ class Repacker:
361
710
  for x in os.scandir(os.path.dirname(executable))])
362
711
  logger.debug('one file mode is %s', bool(self.one_file_mode))
363
712
 
713
+ def build(self):
714
+ obfpath = os.path.normpath(self.ctx.pack_obfpath)
715
+ self.repack(obfpath, self.ctx.runtime_package_name)
716
+
364
717
  def repack(self, obfpath, rtname, entry=None):
365
718
  buildpath = self.buildpath
366
719
  executable = self.executable
@@ -415,6 +768,7 @@ class Repacker:
415
768
  def _fixup_darwin_rtbinary(self, rtbinary, pylib_name):
416
769
  '''Unused since Pyarmor 8.3.0'''
417
770
  from sys import version_info as pyver
771
+ from subprocess import check_output
418
772
  pylib = os.path.normpath(os.path.join('@rpath', pylib_name))
419
773
  output = check_output(['otool', '-L', rtbinary])
420
774
  for line in output.splitlines():
@@ -437,185 +791,3 @@ class Repacker:
437
791
  check_call(cmdlist, stdout=DEVNULL, stderr=DEVNULL)
438
792
  except Exception as e:
439
793
  logger.warning('%s', e)
440
-
441
-
442
- # This patch is used to modify the specfile generate by PyInstaller
443
- #
444
- # It has 2 goals:
445
- #
446
- # 1. Find all the imported modules and packages
447
- #
448
- # Save them to hook script
449
- #
450
- # 2. Search scripts and packages in the path of entry script
451
- #
452
- # All of them will be obfuscated automatically
453
- #
454
- spec_patch_code = '''
455
- import marshal
456
- import os
457
-
458
- from PyInstaller.compat import base_prefix
459
- from sys import prefix, exec_prefix
460
-
461
- exlist = set([base_prefix, prefix, exec_prefix])
462
- exlist = [os.path.normpath(x) for x in exlist]
463
-
464
- src = {src}
465
- sn = len(src) + 1
466
- sdir = os.path.relpath(src)
467
- sdir = '' if sdir == '.' else sdir
468
-
469
- hiddenimports = set([])
470
- plist = set([])
471
- mlist = []
472
-
473
- for name, path, kind in a.pure:
474
- if name.startswith('pyi_rth'):
475
- continue
476
- hiddenimports.add(name)
477
- if path.startswith(src) and not any([path.startswith(x) for x in exlist]):
478
- if name.find('.') == -1 and os.path.basename(path) != '__init__.py':
479
- mlist.append(os.path.join(sdir, path[sn:]))
480
- else:
481
- pkgname = os.path.dirname(path[sn:]).split(os.sep)[0]
482
- plist.add(os.path.join(sdir, pkgname))
483
-
484
- with open({resfile}, 'wb') as f:
485
- marshal.dump(mlist + list(plist), f)
486
- with open({hookscript}, 'w') as f:
487
- f.write("hiddenimports=[%s]" % ", ".join([repr(x) for x in hiddenimports]))
488
- '''
489
-
490
-
491
- class Repacker6:
492
- """New repacker to support PyInstaller 6.0+
493
-
494
- Args:
495
- ctx: build context
496
- mode: onefile or onedir
497
- inputs: main script to pack
498
- output: path to store final bundle, default is `dist`
499
- """
500
-
501
- def __init__(self, ctx, mode, inputs, output):
502
- self.ctx = ctx
503
- self.inputs = inputs
504
- self.output = os.path.normpath(output) if output else 'dist'
505
-
506
- self.script = self.inputs[0]
507
- self.name = os.path.splitext(os.path.basename(self.script))[0]
508
- self.obfpath = os.path.normpath(self.ctx.pack_obfpath)
509
- self.packpath = os.path.normpath(self.ctx.pack_basepath)
510
- self.workpath = os.path.join(self.packpath, 'build')
511
- self.pyicmd = [sys.executable, '-m', 'PyInstaller']
512
- self.autoclean = mode in ('FC', 'DC')
513
- self.modeopt = '-F' if mode in ('onefile', 'F', 'FC') else '-D'
514
- self.init_opts()
515
-
516
- def init_opts(self):
517
- opts = self.ctx.pyi_options
518
- exopts = '--noconfirm', '-y', '--onefile', '-F', '--onedir', '-D'
519
- exvalues = '--distpath', '--specpath', '--workpath'
520
-
521
- self.pyiopts = []
522
-
523
- n = 0
524
- while n < len(opts):
525
- x = opts[n]
526
- if x in ('--name', '-n'):
527
- self.name = opts[n+1]
528
-
529
- if x in exvalues:
530
- n += 1
531
- elif x not in exopts:
532
- self.pyiopts.append(x)
533
- n += 1
534
-
535
- def analysis(self):
536
- """Got imported modules and packages by PyInstaller
537
-
538
- Generate hook script
539
- Return file/dir list need to be obfuscated
540
- """
541
- cmdspec = [sys.executable, '-m', 'PyInstaller.utils.cliutils.makespec']
542
- cmdspec.extend(self.pyiopts)
543
- cmdspec.append(self.script)
544
- logger.debug('%s', ' '.join(cmdspec))
545
- logger.info('call PyInstaller to generate specfile')
546
- check_call(cmdspec, stdout=DEVNULL, stderr=DEVNULL)
547
-
548
- specfile = self.name + '.spec'
549
- rtname = self.ctx.runtime_package_name
550
- resfile = os.path.join(self.packpath, 'resources.list')
551
- hookscript = os.path.join(self.packpath, 'hook-%s.py' % rtname)
552
- self.patch_specfile(specfile, hookscript, resfile)
553
-
554
- cmdlist = self.pyicmd + ['--clean', '--workpath', self.workpath]
555
- cmdlist.append(specfile)
556
- logger.debug('%s', ' '.join(cmdlist))
557
- logger.info('call PyInstaller to analysis, '
558
- 'it may take several minutes ...')
559
- check_call(cmdlist, stdout=DEVNULL, stderr=DEVNULL)
560
-
561
- with open(resfile, 'rb') as f:
562
- reslist = marshal.load(f)
563
-
564
- resoptions = self.ctx.get_res_options('')
565
- excludes = resoptions.get('excludes', '').split()
566
- if excludes:
567
- reslist = [x for x in reslist
568
- if not any([fnmatch(x, pat) for pat in excludes])]
569
- return reslist
570
-
571
- def build(self):
572
- """Generate final bundle to output"""
573
- obfscript = os.path.join(self.obfpath, os.path.basename(self.script))
574
- cmdlist = self.pyicmd + [
575
- '--clean',
576
- '--distpath', self.output,
577
- '--workpath', self.workpath,
578
- '--additional-hooks-dir', self.packpath,
579
- self.modeopt
580
- ]
581
- cmdlist.extend(self.pyiopts)
582
- cmdlist.append(obfscript)
583
-
584
- logger.debug('%s', ' '.join(cmdlist))
585
- logger.info('call PyInstaller to generate final bundle ...\n')
586
- check_call(cmdlist)
587
- logger.info('')
588
- logger.info('the final bundle has been generated to "%s" successfully',
589
- self.output)
590
-
591
- def repack(self, *unused):
592
- """Only for compatible with Repacker"""
593
- self.build()
594
-
595
- def patch_specfile(self, specfile, hookscript, resfile):
596
- # TODO: non-ascii need specify encoding to open file
597
- lines = []
598
- with open(specfile, 'r') as f:
599
- for line in f:
600
- if line.startswith('pyz = PYZ'):
601
- break
602
- lines.append(line)
603
-
604
- lines.append(spec_patch_code.format(
605
- src=repr(os.path.abspath(os.path.dirname(self.script))),
606
- hookscript=repr(os.path.abspath(hookscript)),
607
- resfile=repr(os.path.abspath(resfile))))
608
-
609
- with open(specfile, 'w') as f:
610
- f.write(''.join(lines))
611
-
612
- def check(self):
613
- if os.path.exists(self.output) and self.autoclean:
614
- n = len(os.path.abspath(self.output).split(os.sep))
615
- if n < 3:
616
- prompt = 'Are you sure to remove path "%s" (y/n)? '
617
- choice = input(prompt % self.output).lower()[:1]
618
- if not choice == 'y':
619
- return
620
- logger.info('clean output path "%s"', self.output)
621
- shutil.rmtree(self.output)
@@ -200,7 +200,7 @@ class PycResource(FileResource):
200
200
  class PathResource(Resource):
201
201
 
202
202
  def __init__(self, path, name=None, parent=None):
203
- super().__init__(path, name=name, parent=parent)
203
+ super().__init__(path, name=os.path.basename(path), parent=parent)
204
204
  self.respaths = []
205
205
  self.resfiles = []
206
206
 
@@ -1,6 +1,6 @@
1
1
  from sys import platform
2
2
 
3
- version = '8.5.7'
3
+ version = '8.5.9'
4
4
 
5
5
  # The corresponding version of pytransform.so
6
6
  core_version = 'r52.6'
@@ -294,6 +294,8 @@ def _patch_specfile(obfdist, src, specfile, hookpath=None, encoding=None,
294
294
  end_lines = ("# Patch end.", "", "",)
295
295
 
296
296
  main_lines = (
297
+ "from PyInstaller.config import CONF",
298
+ "_code_cache = getattr(a.pure, '_code_cache', CONF['code_cache'].get(id(a.pure)))",
297
299
  "_src = %s" % repr(os.path.abspath(src)),
298
300
  "_obf = 0",
299
301
  "for i in range(len(a.scripts)):",
@@ -308,9 +310,9 @@ def _patch_specfile(obfdist, src, specfile, hookpath=None, encoding=None,
308
310
  " if a.pure[i][1].startswith(_src):",
309
311
  " x = a.pure[i][1].replace(_src, r'%s')" % p,
310
312
  " if os.path.exists(x):",
311
- " if hasattr(a.pure, '_code_cache'):",
313
+ " if _code_cache:",
312
314
  " with open(x) as f:",
313
- " a.pure._code_cache[a.pure[i][0]] = compile(f.read(), a.pure[i][1], 'exec')",
315
+ " _code_cache[a.pure[i][0]] = compile(f.read(), a.pure[i][1], 'exec')",
314
316
  " a.pure[i] = a.pure[i][0], x, a.pure[i][2]",
315
317
  )
316
318
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.1
2
2
  Name: pyarmor
3
- Version: 8.5.7
3
+ Version: 8.5.9
4
4
  Summary: A tool used to obfuscate python scripts, bind obfuscated scripts to fixed machine or expire obfuscated scripts.
5
5
  Home-page: https://github.com/dashingsoft/pyarmor
6
6
  Author: Jondy Zhao
@@ -0,0 +1 @@
1
+ pyarmor.cli.core~=6.5.2
@@ -113,7 +113,7 @@ setup(
113
113
  },
114
114
 
115
115
  install_requires=[
116
- 'pyarmor.cli.core~=6.5.1'
116
+ 'pyarmor.cli.core~=6.5.2'
117
117
  ],
118
118
 
119
119
  entry_points={
Binary file
Binary file
@@ -1 +0,0 @@
1
- pyarmor.cli.core~=6.5.1
File without changes
File without changes