pyarmor.cli 9.2.6__3-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/__init__.py +46 -0
- pyarmor/cli/__main__.py +812 -0
- pyarmor/cli/batch.py +62 -0
- pyarmor/cli/bootstrap.py +207 -0
- pyarmor/cli/bug.py +137 -0
- pyarmor/cli/command.py +563 -0
- pyarmor/cli/config.py +306 -0
- pyarmor/cli/context.py +790 -0
- pyarmor/cli/core.data.1 +0 -0
- pyarmor/cli/core.data.2 +0 -0
- pyarmor/cli/core.data.3 +0 -0
- pyarmor/cli/default.cfg +564 -0
- pyarmor/cli/docker.py +192 -0
- pyarmor/cli/generate.py +256 -0
- pyarmor/cli/hdinfo.py +81 -0
- pyarmor/cli/merge.py +232 -0
- pyarmor/cli/mixer.py +109 -0
- pyarmor/cli/model.py +460 -0
- pyarmor/cli/plugin.py +292 -0
- pyarmor/cli/project.py +1281 -0
- pyarmor/cli/public_capsule.zip +0 -0
- pyarmor/cli/register.py +1093 -0
- pyarmor/cli/repack.py +793 -0
- pyarmor/cli/resource.py +265 -0
- pyarmor/cli/shell.py +481 -0
- pyarmor_cli-9.2.6.dist-info/METADATA +74 -0
- pyarmor_cli-9.2.6.dist-info/RECORD +30 -0
- pyarmor_cli-9.2.6.dist-info/WHEEL +6 -0
- pyarmor_cli-9.2.6.dist-info/entry_points.txt +3 -0
- pyarmor_cli-9.2.6.dist-info/top_level.txt +1 -0
pyarmor/cli/__main__.py
ADDED
|
@@ -0,0 +1,812 @@
|
|
|
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/main.py
|
|
17
|
+
#
|
|
18
|
+
# @Author: Jondy Zhao (pyarmor@163.com)
|
|
19
|
+
#
|
|
20
|
+
# @Create Date: Thu Jan 12 10:27:05 CST 2023
|
|
21
|
+
#
|
|
22
|
+
import argparse
|
|
23
|
+
import logging
|
|
24
|
+
import os
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
from . import logger, CliError
|
|
28
|
+
from .context import Context
|
|
29
|
+
from .register import Register, WebRegister, check_license_version
|
|
30
|
+
from .config import Configer
|
|
31
|
+
from .shell import PyarmorShell
|
|
32
|
+
from .plugin import Plugin
|
|
33
|
+
from .generate import Builder
|
|
34
|
+
from .bootstrap import check_prebuilt_runtime_library
|
|
35
|
+
from .command import Commander
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _cmd_gen_key(builder, options):
|
|
39
|
+
n = len(options['inputs'])
|
|
40
|
+
if n > 1:
|
|
41
|
+
logger.error('please check online documentation to learn')
|
|
42
|
+
logger.error('how to use command "pyarmor gen key"')
|
|
43
|
+
raise CliError('invalid arguments: %s' % options['inputs'][1:])
|
|
44
|
+
keyname = builder.ctx.outer_keyname
|
|
45
|
+
|
|
46
|
+
logger.info('start to generate outer runtime key "%s"', keyname)
|
|
47
|
+
data = builder.generate_runtime_key(outer=True)
|
|
48
|
+
output = options.get('output', 'dist')
|
|
49
|
+
if output == 'pipe':
|
|
50
|
+
logger.info('return runtime key by pipe')
|
|
51
|
+
return data
|
|
52
|
+
os.makedirs(output, exist_ok=True)
|
|
53
|
+
|
|
54
|
+
target = os.path.join(output, keyname)
|
|
55
|
+
logger.info('write %s', target)
|
|
56
|
+
with open(target, 'wb') as f:
|
|
57
|
+
f.write(data)
|
|
58
|
+
|
|
59
|
+
Plugin.post_key(builder.ctx, target)
|
|
60
|
+
logger.info('generate outer runtime key OK')
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _cmd_gen_runtime(builder, options):
|
|
64
|
+
if len(options['inputs']) > 1:
|
|
65
|
+
logger.error('please check online documentation to learn')
|
|
66
|
+
logger.error('how to use command "pyarmor gen runtime"')
|
|
67
|
+
raise CliError('invalid arguments: %s' % options['inputs'][1:])
|
|
68
|
+
|
|
69
|
+
output = options.get('output', 'dist')
|
|
70
|
+
|
|
71
|
+
logger.info('start to generate runtime package')
|
|
72
|
+
builder.generate_runtime_package(output)
|
|
73
|
+
|
|
74
|
+
keyname = os.path.join(output, builder.ctx.runtime_keyfile)
|
|
75
|
+
logger.info('write "%s"', keyname)
|
|
76
|
+
with open(keyname, 'wb') as f:
|
|
77
|
+
f.write(builder.ctx.runtime_key)
|
|
78
|
+
logger.info('generate runtime package to "%s" OK', output)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def format_gen_args(ctx, args):
|
|
82
|
+
options = {}
|
|
83
|
+
for x in ('recursive', 'findall', 'output', 'no_runtime',
|
|
84
|
+
'enable_bcc', 'enable_jit', 'enable_rft', 'enable_themida',
|
|
85
|
+
'obf_module', 'obf_code', 'assert_import', 'assert_call',
|
|
86
|
+
'mix_str', 'import_prefix', 'restrict_module',
|
|
87
|
+
'platforms', 'outer', 'period', 'expired', 'devices'):
|
|
88
|
+
v = getattr(args, x)
|
|
89
|
+
if v is not None:
|
|
90
|
+
options[x] = v
|
|
91
|
+
|
|
92
|
+
if options.get('platforms'):
|
|
93
|
+
platforms = []
|
|
94
|
+
for item in options['platforms']:
|
|
95
|
+
platforms.extend([x.strip() for x in item.split(',')])
|
|
96
|
+
options['platforms'] = platforms
|
|
97
|
+
elif ctx.runtime_platforms:
|
|
98
|
+
options['platforms'] = ctx.runtime_platforms.split()
|
|
99
|
+
logger.info('get runtime platforms from configuration file')
|
|
100
|
+
if options.get('platforms'):
|
|
101
|
+
logger.info('use runtime platforms: %s', options['platforms'])
|
|
102
|
+
|
|
103
|
+
if args.inputs:
|
|
104
|
+
options['inputs'] = [os.path.normpath(x) for x in args.inputs]
|
|
105
|
+
|
|
106
|
+
if args.use_runtime:
|
|
107
|
+
options['no_runtime'] = True
|
|
108
|
+
options['use_runtime'] = args.use_runtime
|
|
109
|
+
|
|
110
|
+
if (options.get('restrict_module', 0) == 2
|
|
111
|
+
and ctx.cfg['builder'].get('private_module_as_restrict', 0)):
|
|
112
|
+
options['restrict_module'] = 3
|
|
113
|
+
|
|
114
|
+
if options.get('assert_call') or options.get('assert_import'):
|
|
115
|
+
if options.get('restrict_module', 0) < 2:
|
|
116
|
+
logger.debug('implicitly set restrict_module = 2')
|
|
117
|
+
options['restrict_module'] = 2
|
|
118
|
+
|
|
119
|
+
if args.enables:
|
|
120
|
+
for x in args.enables:
|
|
121
|
+
options['enable_' + x] = True
|
|
122
|
+
|
|
123
|
+
if args.prefix:
|
|
124
|
+
options['import_prefix'] = args.prefix
|
|
125
|
+
|
|
126
|
+
if args.no_wrap:
|
|
127
|
+
options['wrap_mode'] = 0
|
|
128
|
+
|
|
129
|
+
if args.includes:
|
|
130
|
+
options['includes'] = ' '.join(args.includes)
|
|
131
|
+
if args.excludes:
|
|
132
|
+
options['excludes'] = ' '.join(args.excludes)
|
|
133
|
+
|
|
134
|
+
if args.bind_data:
|
|
135
|
+
options['user_data'] = args.bind_data
|
|
136
|
+
|
|
137
|
+
if args.pack:
|
|
138
|
+
dist_path = ctx.pack_obfpath
|
|
139
|
+
logger.info('implicitly save obfuscated scripts to "%s"', dist_path)
|
|
140
|
+
options['output'] = dist_path
|
|
141
|
+
|
|
142
|
+
return options
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# Unused
|
|
146
|
+
def check_cross_platform(ctx, platforms):
|
|
147
|
+
rtver = ctx.cfg.get('pyarmor', 'cli.runtime')
|
|
148
|
+
cmd = 'pip install pyarmor.cli.runtime~=%s.0' % rtver
|
|
149
|
+
try:
|
|
150
|
+
from pyarmor.cli import runtime
|
|
151
|
+
except (ImportError, ModuleNotFoundError):
|
|
152
|
+
logger.error('cross platform need package "pyarmor.cli.runtime"')
|
|
153
|
+
logger.error('please run "%s" to fix it', cmd)
|
|
154
|
+
raise CliError('no package "pyarmor.cli.runtime" found')
|
|
155
|
+
|
|
156
|
+
if runtime.__VERSION__ != rtver:
|
|
157
|
+
logger.error('please run "%s" to fix it', cmd)
|
|
158
|
+
raise CliError('unexpected "pyarmor.cli.runtime" version')
|
|
159
|
+
|
|
160
|
+
platnames = []
|
|
161
|
+
for path in runtime.__path__:
|
|
162
|
+
logger.debug('search runtime platforms at: %s', path)
|
|
163
|
+
platnames.extend(os.listdir(os.path.join(path, 'libs')))
|
|
164
|
+
|
|
165
|
+
map_platform = runtime.map_platform
|
|
166
|
+
unknown = set([map_platform(x) for x in platforms]) - set(platnames)
|
|
167
|
+
|
|
168
|
+
if unknown:
|
|
169
|
+
logger.error('please check documentation "References/Environments"')
|
|
170
|
+
raise CliError('unsupported platforms "%s"' % ', '.join(unknown))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def check_gen_context(ctx, args):
|
|
174
|
+
platforms = ctx.runtime_platforms
|
|
175
|
+
if platforms and set(platforms) != set([ctx.pyarmor_platform]):
|
|
176
|
+
if ctx.enable_bcc:
|
|
177
|
+
raise CliError('bcc mode does not support cross platform')
|
|
178
|
+
rtver = ctx.cfg['pyarmor'].get('cli.runtime', '')
|
|
179
|
+
check_prebuilt_runtime_library(platforms, ctx.enable_themida, rtver)
|
|
180
|
+
|
|
181
|
+
elif ctx.enable_themida:
|
|
182
|
+
if not ctx.pyarmor_platform.startswith('windows'):
|
|
183
|
+
raise CliError('--enable-themida only works for Windows')
|
|
184
|
+
rtver = ctx.cfg['pyarmor'].get('cli.runtime', '')
|
|
185
|
+
check_prebuilt_runtime_library([], ['themida'], rtver)
|
|
186
|
+
|
|
187
|
+
if ctx.enable_bcc:
|
|
188
|
+
plat, arch = ctx.pyarmor_platform.split('.')
|
|
189
|
+
if arch not in ('x86_64', 'aarch64', 'x86', 'armv7'):
|
|
190
|
+
raise CliError('bcc mode still not support arch "%s"' % arch)
|
|
191
|
+
|
|
192
|
+
if args.no_runtime and not ctx.runtime_outer:
|
|
193
|
+
raise CliError('--outer is required if using --no_runtime')
|
|
194
|
+
|
|
195
|
+
if ctx.use_runtime and not ctx.runtime_outer:
|
|
196
|
+
if not os.path.exists(ctx.use_runtime):
|
|
197
|
+
raise CliError('no runtime package at "%s"', ctx.use_runtime)
|
|
198
|
+
if any([ctx.runtime_devices, ctx.runtime_period, ctx.runtime_expired]):
|
|
199
|
+
raise CliError('--use-runtime conflicts with any -e, --period, -b')
|
|
200
|
+
keyname = os.path.join(ctx.use_runtime, ctx.runtime_keyfile)
|
|
201
|
+
if not os.path.exists(keyname):
|
|
202
|
+
logger.info('please run `pyarmor gen runtime ...` to generate'
|
|
203
|
+
' shared runtime package at first')
|
|
204
|
+
raise CliError('no runtime key in "%s"', ctx.use_runtime)
|
|
205
|
+
# TBD: check runtime package is not generated by --outer
|
|
206
|
+
|
|
207
|
+
if ctx.runtime_outer and any(
|
|
208
|
+
[ctx.runtime_devices, ctx.runtime_period, ctx.runtime_expired]):
|
|
209
|
+
# Fix issue 2069
|
|
210
|
+
if args.inputs[0].lower() not in ('key', 'k'):
|
|
211
|
+
raise CliError('--outer conflicts with any -e, --period, -b')
|
|
212
|
+
|
|
213
|
+
if args.pack:
|
|
214
|
+
choices = 'onefile', 'onedir', 'F', 'D', 'FC', 'DC'
|
|
215
|
+
if args.pack not in choices and not os.path.isfile(args.pack):
|
|
216
|
+
raise CliError('--pack must be an executable file, specfile, '
|
|
217
|
+
'"onefile" or "onedir"')
|
|
218
|
+
if args.no_runtime:
|
|
219
|
+
raise CliError('--pack conficts with --no-runtime, --use-runtime')
|
|
220
|
+
if ctx.import_prefix:
|
|
221
|
+
raise CliError('--pack conficts with -i, --prefix')
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def cmd_gen(ctx, args):
|
|
225
|
+
options = format_gen_args(ctx, args)
|
|
226
|
+
logger.debug('command options: %s', options)
|
|
227
|
+
check_license_version(ctx)
|
|
228
|
+
|
|
229
|
+
ctx.push(options)
|
|
230
|
+
check_gen_context(ctx, args)
|
|
231
|
+
|
|
232
|
+
builder = Builder(ctx)
|
|
233
|
+
|
|
234
|
+
Plugin.install(ctx)
|
|
235
|
+
if args.inputs[0].lower() in ('key', 'k'):
|
|
236
|
+
return _cmd_gen_key(builder, options)
|
|
237
|
+
elif args.inputs[0].lower() in ('runtime', 'run', 'r'):
|
|
238
|
+
_cmd_gen_runtime(builder, options)
|
|
239
|
+
elif args.pack:
|
|
240
|
+
from .repack import find_packer
|
|
241
|
+
Repacker = find_packer(args.pack)
|
|
242
|
+
packer = Repacker(ctx, args.pack, options['inputs'], args.output)
|
|
243
|
+
packer.check()
|
|
244
|
+
builder.process(options, packer=packer)
|
|
245
|
+
Plugin.post_build(ctx, pack=args.pack)
|
|
246
|
+
packer.build()
|
|
247
|
+
else:
|
|
248
|
+
builder.process(options)
|
|
249
|
+
Plugin.post_build(ctx)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def cmd_cfg(ctx, args):
|
|
253
|
+
scope = 'global' if args.scope else 'local'
|
|
254
|
+
cfg = Configer(ctx, encoding=args.encoding)
|
|
255
|
+
name = 'reset' if args.reset else 'run'
|
|
256
|
+
getattr(cfg, name)(args.options, scope == 'local', args.name)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def cmd_reg(ctx, args):
|
|
260
|
+
if args.buy:
|
|
261
|
+
from webbrowser import open_new_tab
|
|
262
|
+
open_new_tab(ctx.cfg['pyarmor']['buyurl'])
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
if args.device and not args.regfile:
|
|
266
|
+
reg = Register(ctx)
|
|
267
|
+
reg.generate_group_device(args.device)
|
|
268
|
+
logger.info('device file has been generated successfully')
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
regfile = args.regfile
|
|
272
|
+
if not regfile:
|
|
273
|
+
reg = Register(ctx)
|
|
274
|
+
logger.info('Current license information:\n\n%s', reg)
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
if regfile.endswith('.txt') and not args.product:
|
|
278
|
+
logger.error('please use -p to specify product name for this license')
|
|
279
|
+
raise CliError('missing product name')
|
|
280
|
+
|
|
281
|
+
if regfile.endswith('.zip') and args.product:
|
|
282
|
+
logger.error('please do not use -p for non initial registration')
|
|
283
|
+
raise CliError('unwanted product name')
|
|
284
|
+
|
|
285
|
+
upgrade = args.upgrade
|
|
286
|
+
if upgrade:
|
|
287
|
+
if not regfile.endswith('.txt'):
|
|
288
|
+
raise CliError('upgrade need text file "pyarmor-keycode-xxxx.txt"')
|
|
289
|
+
url = 'https://github.com/dashingsoft/pyarmor/issues/980'
|
|
290
|
+
msg = ("",
|
|
291
|
+
"Pyarmor 8 changes EULA and uses new commands",
|
|
292
|
+
"It's different from previous Pyarmor totally",
|
|
293
|
+
"Please read this import notes first:",
|
|
294
|
+
url,
|
|
295
|
+
"Do not upgrade to Pyarmor 8 if don't know what are changed",
|
|
296
|
+
"", "")
|
|
297
|
+
prompt = 'I have known the changes of Pyarmor 8? (yes/no/help) '
|
|
298
|
+
choice = input('\n'.join(msg) + prompt).lower()[:1]
|
|
299
|
+
if choice == 'h':
|
|
300
|
+
import webbrowser
|
|
301
|
+
webbrowser.open(url)
|
|
302
|
+
if not choice == 'y':
|
|
303
|
+
logger.info('abort upgrade')
|
|
304
|
+
return
|
|
305
|
+
|
|
306
|
+
if args.device:
|
|
307
|
+
if not regfile.endswith('.zip'):
|
|
308
|
+
logger.error('invalid registeration file "%s"', regfile)
|
|
309
|
+
raise CliError('please use ".zip" file to register group device')
|
|
310
|
+
regsvr = WebRegister(ctx)
|
|
311
|
+
regsvr.check_request_interval()
|
|
312
|
+
regsvr.request_device_regfile(regfile, args.device)
|
|
313
|
+
logger.info('The device regfile has been generated successfully')
|
|
314
|
+
|
|
315
|
+
elif args.ci:
|
|
316
|
+
regsvr = WebRegister(ctx)
|
|
317
|
+
regsvr.check_request_interval()
|
|
318
|
+
regsvr.request_ci_regfile(regfile)
|
|
319
|
+
|
|
320
|
+
elif regfile.endswith('.zip'):
|
|
321
|
+
reg = Register(ctx)
|
|
322
|
+
logger.info('register "%s"', regfile)
|
|
323
|
+
reg.register_regfile(regfile)
|
|
324
|
+
logger.info('This license registration information:\n\n%s', str(reg))
|
|
325
|
+
|
|
326
|
+
else:
|
|
327
|
+
regsvr = WebRegister(ctx)
|
|
328
|
+
regsvr.check_request_interval(activation=regfile)
|
|
329
|
+
info, msg = regsvr.prepare(regfile, args.product, upgrade=upgrade)
|
|
330
|
+
prompt = 'Are you sure to continue? (yes/no) '
|
|
331
|
+
if args.confirm:
|
|
332
|
+
from time import sleep
|
|
333
|
+
sleep(1.0)
|
|
334
|
+
elif input(msg + prompt) not in ('y', 'yes', 'Y', 'Yes'):
|
|
335
|
+
logger.info('abort registration')
|
|
336
|
+
return
|
|
337
|
+
# Free upgrade to Pyarmor Basic
|
|
338
|
+
if upgrade and not info['upgrade']:
|
|
339
|
+
return regsvr.register(regfile, args.product, upgrade=True)
|
|
340
|
+
|
|
341
|
+
if upgrade:
|
|
342
|
+
regsvr.upgrade_to_pro(regfile, args.product)
|
|
343
|
+
else:
|
|
344
|
+
group = info['lictype'] == 'GROUP'
|
|
345
|
+
regsvr.register(regfile, args.product, group=group)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def cmd_man(ctx, args):
|
|
349
|
+
from subprocess import check_call, check_output, STDOUT
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
m = __import__('pyarmor.man')
|
|
353
|
+
except ModuleNotFoundError:
|
|
354
|
+
logger.info('pyarmor.man is still not installed')
|
|
355
|
+
if input('Install it now? (Y/n) ') not in ('Y', 'y'):
|
|
356
|
+
return
|
|
357
|
+
m = None
|
|
358
|
+
|
|
359
|
+
if m is None:
|
|
360
|
+
try:
|
|
361
|
+
logger.info('installing package "pyarmor.man"...')
|
|
362
|
+
check_output([sys.executable, '-m', 'pip',
|
|
363
|
+
'install', '-U', 'pyarmor.man'],
|
|
364
|
+
stderr=STDOUT)
|
|
365
|
+
logger.info('install package "pyarmor.man" OK')
|
|
366
|
+
except Exception:
|
|
367
|
+
logger.error('install package "pyarmor.man" failed')
|
|
368
|
+
logger.error('please install it manually:')
|
|
369
|
+
logger.error('\tpip install -U pyarmor.man')
|
|
370
|
+
return
|
|
371
|
+
|
|
372
|
+
check_call([sys.executable, '-m', 'pyarmor.man.shell'])
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def main_parser(cmd=None):
|
|
376
|
+
parser = argparse.ArgumentParser(
|
|
377
|
+
prog='pyarmor',
|
|
378
|
+
fromfile_prefix_chars='@',
|
|
379
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
380
|
+
)
|
|
381
|
+
parser.add_argument(
|
|
382
|
+
'-v', '--version', action='store_true',
|
|
383
|
+
help='show version information and exit'
|
|
384
|
+
)
|
|
385
|
+
parser.add_argument(
|
|
386
|
+
'-q', '--silent', action='store_true',
|
|
387
|
+
help='suppress all normal output'
|
|
388
|
+
)
|
|
389
|
+
parser.add_argument(
|
|
390
|
+
'-d', '--debug', action='store_true',
|
|
391
|
+
help='generate debug file "pyarmor.debug.log"'
|
|
392
|
+
)
|
|
393
|
+
parser.add_argument(
|
|
394
|
+
'-i', dest='interactive', action='store_true',
|
|
395
|
+
help=argparse.SUPPRESS,
|
|
396
|
+
)
|
|
397
|
+
parser.add_argument('--home', help=argparse.SUPPRESS)
|
|
398
|
+
|
|
399
|
+
subparsers = parser.add_subparsers(
|
|
400
|
+
title='The most commonly used pyarmor commands are',
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
gen_parser(subparsers)
|
|
404
|
+
reg_parser(subparsers)
|
|
405
|
+
|
|
406
|
+
if cmd:
|
|
407
|
+
cmd.env_parser(subparsers)
|
|
408
|
+
cmd.init_parser(subparsers)
|
|
409
|
+
cmd.build_parser(subparsers)
|
|
410
|
+
|
|
411
|
+
cfg_parser(subparsers)
|
|
412
|
+
man_parser(subparsers)
|
|
413
|
+
|
|
414
|
+
return parser
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def gen_parser(subparsers):
|
|
418
|
+
'''generate obfuscated scripts and all required runtime files
|
|
419
|
+
pyarmor gen <options> <scripts>
|
|
420
|
+
|
|
421
|
+
generate runtime key only
|
|
422
|
+
pyarmor gen key <options>
|
|
423
|
+
|
|
424
|
+
generate runtime package only
|
|
425
|
+
pyarmor gen runtime <options>
|
|
426
|
+
|
|
427
|
+
Refer to
|
|
428
|
+
https://pyarmor.readthedocs.io/en/stable/reference/man.html#pyarmor-gen
|
|
429
|
+
'''
|
|
430
|
+
cparser = subparsers.add_parser(
|
|
431
|
+
'gen',
|
|
432
|
+
aliases=['generate', 'g'],
|
|
433
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
434
|
+
description=gen_parser.__doc__,
|
|
435
|
+
help='generate obfuscated scripts and required runtime files'
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
cparser.add_argument('-O', '--output', metavar='PATH', help='output path')
|
|
439
|
+
|
|
440
|
+
group = cparser.add_argument_group(
|
|
441
|
+
'action arguments'
|
|
442
|
+
).add_mutually_exclusive_group()
|
|
443
|
+
group.add_argument(
|
|
444
|
+
'--pack', metavar='MODE',
|
|
445
|
+
help='specify pack mode, onefile or onedir'
|
|
446
|
+
)
|
|
447
|
+
group.add_argument(
|
|
448
|
+
'--no-runtime', action='store_true', help=argparse.SUPPRESS
|
|
449
|
+
# help='do not generate runtime package (only used with --outer)'
|
|
450
|
+
)
|
|
451
|
+
group.add_argument(
|
|
452
|
+
'--use-runtime', metavar='PATH',
|
|
453
|
+
help='use shared runtime package'
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
group = cparser.add_argument_group('obfuscation arguments')
|
|
457
|
+
group.add_argument(
|
|
458
|
+
'-r', '--recursive', action='store_true', default=None,
|
|
459
|
+
help='search scripts in recursive mode'
|
|
460
|
+
)
|
|
461
|
+
group.add_argument(
|
|
462
|
+
'-a', '--all', dest='findall', action='store_true', default=None,
|
|
463
|
+
help=argparse.SUPPRESS
|
|
464
|
+
)
|
|
465
|
+
group.add_argument(
|
|
466
|
+
'--include', dest='includes', metavar='PATTERN', action='append',
|
|
467
|
+
help=argparse.SUPPRESS
|
|
468
|
+
)
|
|
469
|
+
group.add_argument(
|
|
470
|
+
'--exclude', dest='excludes', metavar='PATTERN', action='append',
|
|
471
|
+
help='Exclude scripts and paths'
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
group.add_argument(
|
|
475
|
+
'--obf-module', type=int, default=None, choices=(0, 1),
|
|
476
|
+
help='obfuscate whole module (default is 1)'
|
|
477
|
+
)
|
|
478
|
+
group.add_argument(
|
|
479
|
+
'--obf-code', type=int, default=None, choices=(0, 1, 2),
|
|
480
|
+
help='obfuscate each function (default is 1)'
|
|
481
|
+
)
|
|
482
|
+
group.add_argument(
|
|
483
|
+
'--no-wrap', action='store_true', default=None,
|
|
484
|
+
help='disable wrap mode',
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
group.add_argument(
|
|
488
|
+
'--mix-str', action='store_true', default=None,
|
|
489
|
+
help='protect string constant',
|
|
490
|
+
)
|
|
491
|
+
group.add_argument(
|
|
492
|
+
'--enable-bcc', action='store_true', default=None,
|
|
493
|
+
help=argparse.SUPPRESS
|
|
494
|
+
)
|
|
495
|
+
group.add_argument(
|
|
496
|
+
'--enable-rft', action='store_true', default=None,
|
|
497
|
+
help=argparse.SUPPRESS
|
|
498
|
+
)
|
|
499
|
+
group.add_argument(
|
|
500
|
+
'--enable-jit', action='store_true', default=None,
|
|
501
|
+
help=argparse.SUPPRESS
|
|
502
|
+
)
|
|
503
|
+
group.add_argument(
|
|
504
|
+
'--enable-themida', action='store_true', default=None,
|
|
505
|
+
help=argparse.SUPPRESS
|
|
506
|
+
)
|
|
507
|
+
group.add_argument(
|
|
508
|
+
'--assert-call', action='store_true', default=None,
|
|
509
|
+
help='assert function is obfuscated'
|
|
510
|
+
)
|
|
511
|
+
group.add_argument(
|
|
512
|
+
'--assert-import', action='store_true', default=None,
|
|
513
|
+
help='assert module is obfuscated'
|
|
514
|
+
)
|
|
515
|
+
group.add_argument(
|
|
516
|
+
'--enable', action='append', dest='enables',
|
|
517
|
+
choices=('jit', 'bcc', 'rft', 'themida'),
|
|
518
|
+
help='enable different obfuscation features',
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
restrict = group.add_mutually_exclusive_group()
|
|
522
|
+
restrict.add_argument(
|
|
523
|
+
'--private', action="store_const", default=None, const=2,
|
|
524
|
+
dest='restrict_module', help='enable private mode for script'
|
|
525
|
+
)
|
|
526
|
+
restrict.add_argument(
|
|
527
|
+
'--restrict', action="store_const", default=None, const=3,
|
|
528
|
+
dest='restrict_module', help='enable restrict mode for package'
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
group = cparser.add_argument_group('runtime package arguments')
|
|
532
|
+
group.add_argument(
|
|
533
|
+
'-i', dest='import_prefix', action='store_const',
|
|
534
|
+
default=None, const=1,
|
|
535
|
+
help='store runtime files inside package'
|
|
536
|
+
)
|
|
537
|
+
group.add_argument(
|
|
538
|
+
'--prefix', metavar='PREFIX',
|
|
539
|
+
help='import runtime package with PREFIX'
|
|
540
|
+
)
|
|
541
|
+
group.add_argument(
|
|
542
|
+
'--platform', dest='platforms', metavar='NAME', action='append',
|
|
543
|
+
help='cross platform obfuscation'
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
group = cparser.add_argument_group('runtime key arguments')
|
|
547
|
+
group.add_argument(
|
|
548
|
+
'--outer', action='store_true', default=None,
|
|
549
|
+
help='enable outer runtime key'
|
|
550
|
+
)
|
|
551
|
+
group.add_argument(
|
|
552
|
+
'-e', '--expired', metavar='DATE',
|
|
553
|
+
help='set expired date'
|
|
554
|
+
)
|
|
555
|
+
group.add_argument(
|
|
556
|
+
'--period', metavar='N', dest='period',
|
|
557
|
+
help='check runtime key periodically'
|
|
558
|
+
)
|
|
559
|
+
group.add_argument(
|
|
560
|
+
'-b', '--bind-device', dest='devices', metavar='DEV', action='append',
|
|
561
|
+
help='bind obfuscated scripts to device'
|
|
562
|
+
)
|
|
563
|
+
group.add_argument(
|
|
564
|
+
'--bind-data', metavar='STRING or @FILENAME',
|
|
565
|
+
help='store user data to runtime key'
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
cparser.add_argument(
|
|
569
|
+
'inputs', metavar='ARG', nargs='+', help='scripts or packages'
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
cparser.set_defaults(func=cmd_gen)
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def cfg_parser(subparsers):
|
|
576
|
+
'''show all options:
|
|
577
|
+
pyarmor cfg
|
|
578
|
+
|
|
579
|
+
show option `OPT` value:
|
|
580
|
+
pyarmor cfg OPT
|
|
581
|
+
|
|
582
|
+
change option value:
|
|
583
|
+
pyarmor cfg OPT=VALUE
|
|
584
|
+
|
|
585
|
+
Refer to
|
|
586
|
+
https://pyarmor.readthedocs.io/en/stable/reference/man.html#pyarmor-cfg
|
|
587
|
+
'''
|
|
588
|
+
|
|
589
|
+
cparser = subparsers.add_parser(
|
|
590
|
+
'cfg',
|
|
591
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
592
|
+
description=cfg_parser.__doc__,
|
|
593
|
+
help='show and config Pyarmor environments',
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
cparser.add_argument(
|
|
597
|
+
'-p', dest='name',
|
|
598
|
+
help='private settings for special module or package'
|
|
599
|
+
)
|
|
600
|
+
cparser.add_argument(
|
|
601
|
+
'-g', '--global', dest='scope', action='store_true',
|
|
602
|
+
help='do everything in global settings, otherwise local settings'
|
|
603
|
+
)
|
|
604
|
+
cparser.add_argument(
|
|
605
|
+
'-s', '--section', help=argparse.SUPPRESS
|
|
606
|
+
)
|
|
607
|
+
cparser.add_argument(
|
|
608
|
+
'-r', '--reset', action='store_true',
|
|
609
|
+
help='reset option to default value'
|
|
610
|
+
)
|
|
611
|
+
cparser.add_argument(
|
|
612
|
+
'--encoding',
|
|
613
|
+
help='specify encoding to read configuration file'
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
cparser.add_argument(
|
|
617
|
+
'options', nargs='*', metavar='option',
|
|
618
|
+
help='option name or "name=value"'
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
cparser.set_defaults(func=cmd_cfg)
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def reg_parser(subparsers):
|
|
625
|
+
'''register Pyarmor or upgrade Pyarmor license
|
|
626
|
+
|
|
627
|
+
At the first time to register Pyarmor, `-p` (product name) should be
|
|
628
|
+
set. For non-commercial use, set it to "non-profits". The product name
|
|
629
|
+
can't be changed after initial registration.
|
|
630
|
+
|
|
631
|
+
Refer to
|
|
632
|
+
https://pyarmor.readthedocs.io/en/stable/reference/man.html#pyarmor-reg
|
|
633
|
+
'''
|
|
634
|
+
cparser = subparsers.add_parser(
|
|
635
|
+
'reg',
|
|
636
|
+
aliases=['register', 'r'],
|
|
637
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
638
|
+
description=reg_parser.__doc__,
|
|
639
|
+
help='register Pyarmor or upgrade old Pyarmor license'
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
cparser.add_argument(
|
|
643
|
+
'-r', '--regname', metavar='NAME',
|
|
644
|
+
help=argparse.SUPPRESS
|
|
645
|
+
)
|
|
646
|
+
cparser.add_argument(
|
|
647
|
+
'-p', '--product', metavar='NAME',
|
|
648
|
+
help='bind license to this product'
|
|
649
|
+
)
|
|
650
|
+
cparser.add_argument(
|
|
651
|
+
'-u', '--upgrade', action='store_true',
|
|
652
|
+
help='upgrade old Pyarmor license'
|
|
653
|
+
)
|
|
654
|
+
cparser.add_argument(
|
|
655
|
+
'-g', '--device', metavar='ID', type=int, choices=range(1, 101),
|
|
656
|
+
help='device id (1-100) in group license'
|
|
657
|
+
)
|
|
658
|
+
cparser.add_argument(
|
|
659
|
+
'--buy', action='store_true',
|
|
660
|
+
help='open buy link in default web browser'
|
|
661
|
+
)
|
|
662
|
+
cparser.add_argument(
|
|
663
|
+
'-y', '--confirm', action='store_true',
|
|
664
|
+
help=argparse.SUPPRESS
|
|
665
|
+
)
|
|
666
|
+
cparser.add_argument(
|
|
667
|
+
'-C', '--CI', action='store_true', dest='ci',
|
|
668
|
+
help='request license regfile for CI pipeline'
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
cparser.add_argument(
|
|
672
|
+
'regfile', nargs='?', metavar='FILE',
|
|
673
|
+
help='pyarmor-regcode-xxx.txt or pyarmor-regfile-xxxx.zip'
|
|
674
|
+
)
|
|
675
|
+
cparser.set_defaults(func=cmd_reg)
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
def man_parser(subparsers):
|
|
679
|
+
'''Start Pyarmor Man shell
|
|
680
|
+
|
|
681
|
+
Pyarmor Man is designed to help Pyarmor users to learn and use
|
|
682
|
+
Pyarmor, to find solution quickly when something is wrong, to
|
|
683
|
+
report bug by template in order to save both Pyarmor team's
|
|
684
|
+
and users' time.
|
|
685
|
+
'''
|
|
686
|
+
cparser = subparsers.add_parser(
|
|
687
|
+
'man',
|
|
688
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
689
|
+
description=man_parser.__doc__,
|
|
690
|
+
help='start Pyarmor Man shell'
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
cparser.set_defaults(func=cmd_man)
|
|
694
|
+
return cparser
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def log_settings(ctx, args):
|
|
698
|
+
import logging.config
|
|
699
|
+
logging.config.fileConfig(ctx.default_config)
|
|
700
|
+
|
|
701
|
+
if args.silent:
|
|
702
|
+
logger.setLevel(logging.ERROR)
|
|
703
|
+
|
|
704
|
+
elif args.debug:
|
|
705
|
+
logger.setLevel(logging.DEBUG)
|
|
706
|
+
logger.handlers[1].setLevel(logging.DEBUG)
|
|
707
|
+
|
|
708
|
+
if ctx.cfg.getboolean('builder', 'enable_trace'):
|
|
709
|
+
level = logging.DEBUG if args.debug else logging.INFO
|
|
710
|
+
logging.getLogger('trace').setLevel(level)
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def log_bug(e):
|
|
714
|
+
parser = main_parser(cmd=Commander())
|
|
715
|
+
args = parser.parse_args(sys.argv[1:])
|
|
716
|
+
ctx = Context(*get_home_paths(args))
|
|
717
|
+
cmdline = ' '.join(sys.argv)
|
|
718
|
+
buglog = logging.getLogger('cli.bug')
|
|
719
|
+
|
|
720
|
+
buglog.info('[BUG]: %s\n', e)
|
|
721
|
+
buglog.info('## Command Line\n%s\n', cmdline)
|
|
722
|
+
buglog.info('## Environments', )
|
|
723
|
+
buglog.info('Python %d.%d.%d', *sys.version_info[:3])
|
|
724
|
+
buglog.info('Pyarmor %s', ctx.version_info())
|
|
725
|
+
buglog.info('Platform %s', ctx.pyarmor_platform)
|
|
726
|
+
buglog.info('Native %s', ctx.native_platform)
|
|
727
|
+
buglog.info('Home %s', ctx.home_path)
|
|
728
|
+
buglog.info('')
|
|
729
|
+
|
|
730
|
+
if not isinstance(e, CliError):
|
|
731
|
+
from traceback import format_exc
|
|
732
|
+
buglog.info('## Traceback\n%s\n', format_exc())
|
|
733
|
+
|
|
734
|
+
# reference/errors.html
|
|
735
|
+
# reference/solutions.html
|
|
736
|
+
# questions.html
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def print_version(ctx):
|
|
740
|
+
reg = Register(ctx)
|
|
741
|
+
info = 'Pyarmor %s' % ctx.version_info(), '', str(reg)
|
|
742
|
+
print('\n'.join(info))
|
|
743
|
+
|
|
744
|
+
reg.check_group_license()
|
|
745
|
+
check_license_version(ctx, silent=True)
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
def get_home_paths(args):
|
|
749
|
+
home = args.home if args.home else os.getenv('PYARMOR_HOME')
|
|
750
|
+
if not home:
|
|
751
|
+
home = os.path.join('~', '.pyarmor')
|
|
752
|
+
elif home.startswith(','):
|
|
753
|
+
home = os.path.join('~', '.pyarmor') + home
|
|
754
|
+
home = os.path.abspath(os.path.expandvars(os.path.expanduser(home)))
|
|
755
|
+
return (home + ',,,').split(',')[:4]
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def main_entry(argv):
|
|
759
|
+
cmd = Commander()
|
|
760
|
+
parser = main_parser(cmd)
|
|
761
|
+
args = parser.parse_args(argv)
|
|
762
|
+
|
|
763
|
+
ctx = cmd.ctx = Context(*get_home_paths(args))
|
|
764
|
+
log_settings(ctx, args)
|
|
765
|
+
|
|
766
|
+
x, y = sys.version_info[:2]
|
|
767
|
+
if not (x == 3 and y > 6 and y < 16):
|
|
768
|
+
raise CliError('Python %s.%s is not supported' % (x, y))
|
|
769
|
+
|
|
770
|
+
if ctx.py_gil_disabled:
|
|
771
|
+
raise CliError('Pyarmor does not support free-threading Python')
|
|
772
|
+
|
|
773
|
+
if args.version:
|
|
774
|
+
print_version(ctx)
|
|
775
|
+
parser.exit()
|
|
776
|
+
|
|
777
|
+
if args.interactive:
|
|
778
|
+
return PyarmorShell(ctx).cmdloop()
|
|
779
|
+
|
|
780
|
+
logger.info('Python %d.%d.%d', *sys.version_info[:3])
|
|
781
|
+
logger.info('Pyarmor %s', ctx.version_info())
|
|
782
|
+
logger.info('Platform %s', ctx.pyarmor_platform)
|
|
783
|
+
|
|
784
|
+
logger.debug('native platform %s', ctx.native_platform)
|
|
785
|
+
logger.debug('home path: %s', ctx.home_path)
|
|
786
|
+
logger.debug('args: %s', argv)
|
|
787
|
+
|
|
788
|
+
if hasattr(args, 'func'):
|
|
789
|
+
return args.func(ctx, args)
|
|
790
|
+
else:
|
|
791
|
+
parser.print_help()
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def map_error(e):
|
|
795
|
+
if str(e) == 'invalid license token':
|
|
796
|
+
return ('invalid license token or failed to update license token. '
|
|
797
|
+
'please make sure this device is online '
|
|
798
|
+
'and can connect to Pyarmor License Server')
|
|
799
|
+
return e
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def main():
|
|
803
|
+
try:
|
|
804
|
+
main_entry(sys.argv[1:])
|
|
805
|
+
except Exception as e:
|
|
806
|
+
logger.error(map_error(e))
|
|
807
|
+
log_bug(e)
|
|
808
|
+
sys.exit(1 if isinstance(e, CliError) else 2)
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
if __name__ == '__main__':
|
|
812
|
+
main()
|