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.
@@ -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()