From 281e2fe49ceba15cb71f74f6fa8a0d7d3c7c0f21 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 6 Oct 2024 23:04:39 +0800 Subject: [PATCH 001/337] Change version to 8.6.0 --- docs/conf.py | 4 ++-- src/cli/__init__.py | 2 +- src/cli/default.cfg | 6 +++--- src/config.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 4fd2be47..94e31c33 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,9 +57,9 @@ # built documents. # # The short X.Y version. -version = '8.5' +version = '8.6' # The full version, including alpha/beta/rc tags. -release = '8.5.12' +release = '8.6.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 695f3fb4..e38247a7 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '8.5.12' +__VERSION__ = '8.6.0' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 9dba93c7..2da4e0bb 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -2,8 +2,8 @@ ;; Pyarmor version major = 8 -minor = 5 -patch = 12 +minor = 6 +patch = 0 ;; Compatible core version cli.core = 6.5.3 @@ -17,7 +17,7 @@ cli.core = 6.5.3 timeout = 6 regurl = https://api.dashingsoft.com/product/key/enter/%s/? -buyurl = https://order.mycommerce.com/product?vendorid=200089125&productid=301044051 +buyurl = https://jondy.github.io/paypal.html [logging] diff --git a/src/config.py b/src/config.py index d594369d..5062393f 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '8.5.12' +version = '8.6.0' # The corresponding version of pytransform.so core_version = 'r52.6' @@ -43,7 +43,7 @@ key_url = 'https://api.dashingsoft.com/product/key/%s/query' reg_url = 'https://api.dashingsoft.com/product/key/activate/%s/' -buy_url = 'https://order.shareit.com/cart/add?vendorid=200089125&PRODUCT[300871197]=1' +buy_url = 'https://jondy.github.io/paypal.html' help_url = 'https://pyarmor.readthedocs.io/{lang}/v%s/{page}' % version sppmode_info = { From 5081b8f50219d5f1288755979587dbc07055eb6f Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 6 Oct 2024 23:49:34 +0800 Subject: [PATCH 002/337] Change default shopping to Paypal --- docs/_common_definitions.txt | 1 + docs/licenses.rst | 88 ++++++------------------------------ docs/questions.rst | 11 ++++- 3 files changed, 23 insertions(+), 77 deletions(-) diff --git a/docs/_common_definitions.txt b/docs/_common_definitions.txt index db69a6b0..c7337139 100644 --- a/docs/_common_definitions.txt +++ b/docs/_common_definitions.txt @@ -4,6 +4,7 @@ .. _Pyarmor Doc: https://pyarmor.readthedocs.io/en/stable/ .. _Pyarmor 7.x Doc: https://pyarmor.readthedocs.io/en/v7.7/ .. _Pyarmor EULA: https://github.com/dashingsoft/pyarmor/blob/master/LICENSE +.. _MyCommerce Shopping: https://order.mycommerce.com/product?vendorid=200089125&productid=301044051 .. |pyarmor| replace:: :doc:`pyarmor ` .. |Pyarmor| replace:: :term:`Pyarmor` diff --git a/docs/licenses.rst b/docs/licenses.rst index 3fc77606..279e47ef 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -172,13 +172,13 @@ License features Purchasing license ================== -Open shopping cart in any web browser: +If you have Pyarmor 8.6+ installed, this command also could open shopping cart:: - https://order.mycommerce.com/product?vendorid=200089125&productid=301044051 + $ pyarmor reg --buy -If you have Pyarmor 8.0+ installed, this command also could open shopping cart:: +Open shopping cart in any web browser: - $ pyarmor reg --buy + https://jondy.github.io/paypal.html In the shopping cart, select License Type and complete the payment online. @@ -217,15 +217,19 @@ Following the guide in activation file to take the purchased license effects, or Refund policy ------------- -If activation file isn't used, and purchasing date is in 30 days, refund is acceptable. Please +If activation file isn't used, and purchasing date is in 30 days, refund is acceptable. -1. Email to Ordersupport@mycommerce.com with order information and ask for refund. -2. Or click `FindMyOrder page`_ to submit refund request +- If purchasing order from MyCommerce: -Out of 30 days, or activation file has been used, refund request will be rejected. + 1. Email to Ordersupport@mycommerce.com with order information and ask for refund. + 2. Or click `FindMyOrder page`_ to submit refund request -.. _FindMyOrder page: https://www.findmyorder.com/store?Action=DisplayEmailCustomerServicePage&Env=BASE&Locale=en_US&SiteID=findmyor +- If purchasing order from reseller, contact your reseller +- For other cases, email to pyarmor@163.com + +Out of 30 days, or activation file has been used, refund request will be rejected. + .. _FindMyOrder page: https://www.findmyorder.com/store?Action=DisplayEmailCustomerServicePage&Env=BASE&Locale=en_US&SiteID=findmyor .. _upgrading old license: @@ -291,70 +295,4 @@ After successful registration, all obfuscations will automatically apply this li If old license is used by many products (mainly old personal license), only one product could be used after upgrading. For the others, it need purchase new license. -.. - Upgrading old license to Pyarmor Pro - ------------------------------------ - - Upgrading old license to Pyarmor Pro needs extra fees. - - .. list-table:: Table-3. Upgrade fee from old license - :header-rows: 1 - - * - License Type - - Upgrading fee($) - - Remark - * - Basic - - 0 - - following new EULA and match some conditions - * - Pro - - 50 - - - * - Group - - N/A - - - - Open shopping cart in any web browser: - - https://order.mycommerce.com/product?vendorid=200089125&productid=301044051 - - If you have Pyarmor 8.2+ installed, this command also could open shopping cart:: - - $ pyarmor reg --buy - - In the shopping cart, select ``Pyarmor-upgrade`` and complete the payment online. - - A file named ``pyarmor-regcode-to-pro.txt`` will be sent by email immediately after payment is completed successfully. - - This file includes the guide to upgrade old license to Pyarmor Pro. - - Internet connection and Pyarmor 8.2+ are required to upgrade old license, check Pyarmor version by this command - - pyarmor -v - - Check old license - - pyarmor-7 -v - - If no old license found, register old license first - - pyarmor-7 register pyarmor-regcode-xxxx.txt - - Assume this license will be used to obfuscate product ``XXX``, run this command:: - - $ pyarmor reg -u -p "XXX" pyarmor-regcode-xxxx.txt - - Check the upgraded license information:: - - $ pyarmor -v - - After upgrade successfully, the file ``pyarmor-regcode-to-pro.txt`` is invalid. A new :term:`registration file` like :file:`pyarmor-regfile-xxxx.zip` will be generated at the same time. - - In other devices using this new :term:`registration file` to register Pyarmor by this command:: - - $ pyarmor reg pyarmor-regfile-xxxx.zip - - After successful registration, all obfuscations will automatically apply this license, and each obfuscation requires online license verification. - - If old license is used by many products (mainly old personal license), only one product could be used after upgrading. For the others, it need purchase new license. - .. include:: _common_definitions.txt diff --git a/docs/questions.rst b/docs/questions.rst index 4e8cb32c..856d8c0c 100644 --- a/docs/questions.rst +++ b/docs/questions.rst @@ -398,6 +398,7 @@ Upgrading __ https://pyarmor.readthedocs.io/en/stable/topic/repack.html __ https://github.com/dashingsoft/pyarmor/discussions/1107 +.. Purchasing ========== @@ -405,8 +406,14 @@ Purchasing If this order isn't activated and in 30 days since purchasing, you can refund the order by one of ways - 1. Email to Ordersupport@mycommerce.com with order information and ask for refund. - 2. Or click `FindMyOrder page`_ to submit refund request + * If purchasing order from MyCommerce: + + 1. Email to Ordersupport@mycommerce.com with order information and ask for refund. + 2. Or click `FindMyOrder page`_ to submit refund request + + * If purchasing order from reseller, contact your reseller + + * For other cases, email to pyarmor@163.com .. _FindMyOrder page: https://www.findmyorder.com/store?Action=DisplayEmailCustomerServicePage&Env=BASE&Locale=en_US&SiteID=findmyor From eefe8e39f5ca21f65bdd57b5141a52c788c0a2df Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 6 Oct 2024 23:51:16 +0800 Subject: [PATCH 003/337] Remove unused discount section --- docs/licenses.rst | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index 279e47ef..c535caee 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -249,27 +249,6 @@ If failed to upgrade the old license, please purchase new license to use Pyarmor The old license can't be upgraded to Pyarmor Pro and Group. -.. - But there is discount for old users to purchase Pyarmor Pro. - - .. list-table:: Table-3. Pyarmor Pro Discount for Old Users - :header-rows: 1 - - * - Old License Purchased Date - - Discount(%) - - Remark - * - Between 2022-06-01 and 2023-05-31 - - 40 - - - * - Between 2021-06-01 and 2022-05-31 - - 30 - - - * - Before 2021-05-31 - - 20 - - - - Please send your order no. to pyarmor@163.com to request discount coupon, this request must be sent from registration email. - Upgrading old license to Pyarmor Basic -------------------------------------- From e4cdcc1791b0e21c38636ce2ce1ad7042840758c Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 7 Oct 2024 07:31:04 +0800 Subject: [PATCH 004/337] Refine shopping cart link --- docs/licenses.rst | 4 ++-- src/cli/default.cfg | 2 +- src/config.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index c535caee..00629802 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -176,9 +176,9 @@ If you have Pyarmor 8.6+ installed, this command also could open shopping cart:: $ pyarmor reg --buy -Open shopping cart in any web browser: +Open `Pyarmor Shopping Cart`_ in any web browser: - https://jondy.github.io/paypal.html + https://jondy.github.io/paypal/index.html In the shopping cart, select License Type and complete the payment online. diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 2da4e0bb..019373b0 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -17,7 +17,7 @@ cli.core = 6.5.3 timeout = 6 regurl = https://api.dashingsoft.com/product/key/enter/%s/? -buyurl = https://jondy.github.io/paypal.html +buyurl = https://jondy.github.io/paypal/index.html [logging] diff --git a/src/config.py b/src/config.py index 5062393f..95572017 100755 --- a/src/config.py +++ b/src/config.py @@ -43,7 +43,7 @@ key_url = 'https://api.dashingsoft.com/product/key/%s/query' reg_url = 'https://api.dashingsoft.com/product/key/activate/%s/' -buy_url = 'https://jondy.github.io/paypal.html' +buy_url = 'https://jondy.github.io/paypal/index.html' help_url = 'https://pyarmor.readthedocs.io/{lang}/v%s/{page}' % version sppmode_info = { From b05cb44140a917bf0c4cafcab68397a6490a67db Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 7 Oct 2024 07:31:26 +0800 Subject: [PATCH 005/337] Refine shopping cart link --- docs/_common_definitions.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_common_definitions.txt b/docs/_common_definitions.txt index c7337139..54a97b04 100644 --- a/docs/_common_definitions.txt +++ b/docs/_common_definitions.txt @@ -5,6 +5,7 @@ .. _Pyarmor 7.x Doc: https://pyarmor.readthedocs.io/en/v7.7/ .. _Pyarmor EULA: https://github.com/dashingsoft/pyarmor/blob/master/LICENSE .. _MyCommerce Shopping: https://order.mycommerce.com/product?vendorid=200089125&productid=301044051 +.. _Pyarmor Shopping Cart: https://jondy.github.io/paypal/index.html .. |pyarmor| replace:: :doc:`pyarmor ` .. |Pyarmor| replace:: :term:`Pyarmor` From e4341429c5d832170e99b6dc5afa451bc42ba9df Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 7 Oct 2024 18:02:37 +0800 Subject: [PATCH 006/337] Rewrite license doc for Pyarmor 9 Major changes: 1. Add new license type Pyarmor CI License 2. Pyarmor Pro License could not be used in CI/CD in Pyarmor 9 3. Add new option for command "reg" to support Pyarmor License in CI/CD 4. Pyarmor Basic License need use new way to be registerred in CI/CD --- docs/how-to/ci.rst | 73 +--------- docs/how-to/register.rst | 110 ++++++++++++-- docs/licenses.rst | 306 ++++++++++++++++----------------------- 3 files changed, 231 insertions(+), 258 deletions(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index e428d1c3..dd338a68 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -4,20 +4,15 @@ Using Pyarmor in CI Pipeline ============================== -Pyarmor could be used in CI/CD pipeline directly, but there are some limitions: +.. deprecated:: 9.0 + Use Pyarmor Basic or CI License directly instead. -- Group License generally doesn't work in CI/CD pipeline -- It only allows 3 runs in 1 minutes for Basic/Pro license -- It only allows 100 runs in 24 hours for Basic/Pro license - - - If need more than 100 runs, refer to :ref:`Using Pyarmor CI Quota` - -Pyarmor recommends to use Pyarmor in CI/CD pipeline by this way: +Pyarmor also could be used in CI/CD pipeline by this way: - First obfuscate the scripts by a few runner and store them to another branch like `master-obf` - Then all the other runners continue the rest pipeline based on this branch like before -Because only first step runners need register Pyarmor, so it could solve run limitions in most of cases. +Because only first step runners need register Pyarmor, so it could solve run limitions prior to Pyarmor 9.0 Suppose test-project locates at `https://github.com/dashingsoft/test-project`, the directory tree as follows:: @@ -67,67 +62,13 @@ For all the other runners, they need not install Pyarmor, just checkout branch ` Using Pyarmor CI Quota ====================== +.. versionremoved:: 9.0 + This feature has been removed. + Pyarmor CI Quota is one experimental solution for this issue: - Basic/Pro license only allow 100 runs in CI/CD pipeline in 24 hours Each ci quota allows 2,000 extra runs after 100 runs in 24 hours. -.. list-table:: Table-1. Pyarmor CI Quota Price - :header-rows: 1 - - * - Extra Runs - - Net Price($) - - Remark - * - 2,000 - - 10 - - - -1. First check your license no. by `pyarmor -v`:: - - $ pyarmor -v - Pyarmor 8.5.11 (pro), 005068, btarmor - - License Type : pyarmor-pro - License No. : pyarmor-vax-005068 - License To : Tester - License Product : btarmor - ... - -2. Then purchasing Pyarmor CI Quota in MyCommerce website - - https://order.mycommerce.com/product?vendorid=200089125&productid=301123145 - -3. When placing order, fill "License to" with Pyarmor License No. For example, ``pyarmor-vax-005068`` - -4. Once payment is completed, this license will has one Pyarmor CI Quota - - If no more than 100 runs in 24 hours, it doesn't consume CI Quota. - -.. list-table:: Table-2. Pyarmor CI Quota Usage Example - :header-rows: 1 - - * - Date - - Runs in CI/CD pipeline - - Used Quota - - Left Quota - - Remark - * - 2024-05-01 - - - - - - 2000 - - Init quota - * - 2024-05-02 - - 80 - - 0 - - 2000 - - No consume quota because 80 < 100 - * - 2024-05-03 - - 120 - - 20 - - 1980 - - Consume quota 20 = 120 - 100 - -5. When ci quota is exhausted, it need purchase new Pyarmor CI Quota - .. include:: ../_common_definitions.txt diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 3f66bed7..f574be58 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -82,11 +82,34 @@ After successful registration, all obfuscations will automatically apply this li Registering in Docker or CI pipeline ------------------------------------ -It's no problem to run Pyarmor in Docker or CI pipeline to obfuscate user's application by Pyarmor Basic or Pro license. Register pyarmor with :file:`pyarmor-regfile-xxxx.zip` same as above. **But It's not allowed to distribute pyarmor self and any Pyarmor License to your customers** +.. versionchanged:: 9.0 + The Pyarmor Pro License couldn't be used in CI/CD pipeline since 9.0, use Pyarmor CI License instead. -Don't run too many build dockers, maximum is 100 in 24 hours. If more than 100 runs one day, please use Pyarmor Group License. +Refer to :ref:`Using Pyarmor in CI Pipeline` -And if need run more than 3 build dockers, it's better to start each docker container or runner every 30 seconds, too many register requests in same time may be rejected by license server. +.. _Check Device For Group License: + +Check Device For Group License +============================== + +Check one device works for group license by this way: + +* First install Pyarmor 8.4.0+ trial version in this device +* Got machine id by the following command:: + + $ pyarmor reg -g 1 + ... + INFO current machine id is "mc92c9f22c732b482fb485aad31d789f1" + INFO device file has been generated successfully + +* Reboot this device, check machine id is same or not +* If machine id is same after each reboot, group license works in this device. Otherwise group license doesn't work in this device. + +For docker container, please check docker host as above. Only if docker host could work with group license, unlimited docker containers could be run in this docker host, refer to :doc:`how-to/register` section ``run unlimited dockers in offline device`` + +**If machine id of docker host is changed after reboot, group license doesn't work in any docker container** + +Most of physics machine, cloud server or VM like qemu, virtual box, vmware with same disk image work with Group license. Most of runners in CI/CD pipeline could not use Group License. Using group license =================== @@ -95,7 +118,7 @@ Using group license Each :term:`Pyarmor Group` could have 100 offline devices, each device has its own number, from 1 to 100. -Only the machine id of device is not changed after reboot, it could be used as group device. Most of physics machine, cloud server or VM like Qemu, Virtual box, Vmware with same disk image work with Group license. If using group in CI pipeline, the default runner doesn't work, but something like `self-host runner`__ may work. +Only the machine id of device is not changed after reboot, it could be used as group device. Most of physics machine, cloud server or VM like Qemu, Virtual box, Vmware with same disk image work with Group license. Refer to :ref:`Check Device For Group License` The allocated device No. is never free, if a device is reinstalled, it need allocate new one. @@ -379,13 +402,37 @@ If run `pyarmor-auth` in Linux VM or WSL, please check group license could work Using group license in CI pipeline ---------------------------------- -Pyarmor group license could not be used in CI pipeline with default runners, but it may work on something like `self-host runner`__, please check CI documentation for more information. +.. deprecated:: 9.0 + Use Pyarmor CI License instead, refer to :ref:`Using Pyarmor in CI Pipeline` -The other workaround is that first obfuscating scripts in docker container like above, then create a new branch to store obfuscated scripts in VC server. +Pyarmor Group License could not be used in CI pipeline generally. -CI pipeline could get obfuscated scripts from this new branch, and start workflow as they're normal Python scripts. +.. _using pyarmor in ci pipeline: -__ https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners +Using Pyarmor in CI Pipeline +============================= + +.. versionadded:: 9.0 + +For free version, it's enough to install pyarmor by `pip install pyarmor` in the pipeline. + +Pyarmor Pro License and Pyarmor Group License couldn't be used in CI/CD pipeline. + +Pyarmor Basic and CI License could be used in CI/CD pipeline by this way + +- First generate ci regfile like pyarmor-CIxxxx-202410240512.zip:: + + pyarmor reg -C pyarmor-regfile-xxxx.zip + + "pyarmor reg -C" only could be used 3 times in 24 hours, so do not use it in the pipeline + +- Then copy this file to each pipeline and register Pyarmor:: + + pyarmor reg pyarmor-CIxxxx-202410240512.zip + +Make sure pipeline is online because it need do some online verification. + +This ci regfile will be expired in 3 days, please generate new one on demand. Using multiple Pyarmor Licenses in same device ============================================== @@ -415,13 +462,56 @@ But in the following versions something is changed - Some old licenses can be upgraded to Basic License freely, refer to :ref:`upgrade old license ` - Old license can't be upgraded to Pro or Group License -- **Pyarmor 8.6** For Group License it need generate device regfile again with Pyarmor 8.6+. The old device regfile which is generated in prior to Pyarmor 8.6 doesn't work in Pyarmor 8.6+ +- **Pyarmor 9.0** For Group License it need generate device regfile again with Pyarmor 9.0+. The old device regfile which is generated in prior to Pyarmor 8.6 doesn't work in Pyarmor 9.0+ - - First upgrade Pyarmor to 8.6+ in online device + - First upgrade Pyarmor to 9.0+ in online device - Then generate device regfile as first time. For example, generate device regfile ``pyarmor-device-regfile-6000.1.zip`` for device no. 1:: pyarmor reg -g 1 /path/to/pyarmor-regfile-6000.zip - Finally, replace old one with new one +.. _upgrading old license: + +Upgrading old license +===================== + +Not all the old license could be upgraded to latest version. + +The old license could be upgraded to Pyarmor Basic freely only if it matches these conditions: + +* Following new `Pyarmor EULA`_ +* The license no. starts with ``pyarmor-vax-`` +* The original activation file ``pyarmor-regcode-xxxx.txt`` exists and isn't used more than 100 times +* The old license is purchased before June 1, 2023. In principle, the old license purchased after Pyarmor 8 is available could not be upgraded to new license. + +If failed to upgrade the old license, please purchase new license to use Pyarmor latest version. + +The old license can't be upgraded to Pyarmor Pro and Group. + +Upgrading old license to Pyarmor Basic +-------------------------------------- + +First find the activation file ``pyarmor-regcode-xxxx.txt``, which is sent to registration email when purchasing the license. + +Next install Pyarmor 8.2+, according to new `EULA of Pyarmor`_, each license is only for one product. + +Assume this license will be used to obfuscate product ``XXX``, run this command:: + + $ pyarmor reg -u -p "XXX" pyarmor-regcode-xxxx.txt + +Check the upgraded license information:: + + $ pyarmor -v + +After upgrade successfully, do not use activation file ``pyarmor-regcode-xxxx.txt`` again, it's invalid now. A new :term:`registration file` like :file:`pyarmor-regfile-xxxx.zip` will be generated at the same time. + +In other devices using this new :term:`registration file` to register Pyarmor by this command:: + + $ pyarmor reg pyarmor-regfile-xxxx.zip + +After successful registration, all obfuscations will automatically apply this license, and each obfuscation requires online license verification. + +If old license is used by many products (mainly old personal license), only one product could be used after upgrading. For the others, it need purchase new license. + .. include:: ../_common_definitions.txt diff --git a/docs/licenses.rst b/docs/licenses.rst index 00629802..80134716 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -9,165 +9,155 @@ .. highlight:: console -Introduction -============ - This documentation is only apply to Pyarmor_ 8.0 plus. -Pyarmor is published as shareware, free trial version never expires, but there are some limitations: +Pyarmor is published in `PyPI`_, free trial version never expires. Try it by the following commands:: -(1) Can not obfuscate big scripts -(2) Can not use feature mix-str to obfuscate string constant in scripts -(3) Can not use RFT Mode, BCC Mode -(4) Can not be used for any commercial product. If the total sale income of this product is less than 30x license fees, this software could be used temporarily. -(5) Can not change runtime package name "pyarmor_runtime_000000" -(6) Can not be used to provide obfuscation service in any form, in short this software can't be used to obfuscate the scripts of others -(7) Can not use obf-code > 1 + $ pip install pyarmor + $ pyarmor gen foo.py + $ python dist/foo.py -These limitations can be unlocked by different License Types except (6). All Pyarmor Licenses are only used to obfuscate the scripts written by licensee, in any case they couldn't be used to obfuscate the scripts not written by licensee. +There are some limitations in free version, for example, can't obfuscate big scripts etc. These limitations can be unlocked by different license types License types ============= -Pyarmor has 3 kind of licenses: - -.. glossary:: +.. versionadded:: 9.0 + Pyarmor CI License - Pyarmor Basic +Pyarmor has 4 kind of licenses: - Basic license could unlock limitations (1) (2) (4) (5) (7). +- Basic +- Pro +- Group +- CI - Each obfuscation need online verify license. - - Pyarmor Pro - - Pro license could unlock limitations (1) (2) (3) (4) (5) (7). +.. list-table:: Table-1. Compare Different Licenses + :widths: 40 12 12 12 12 12 + :header-rows: 1 + :stub-columns: 1 + :align: center + + * - Feature + - Free + - Basic + - Pro + - Group + - CI + * - Big Script / Mix String [#]_ + - + - Y + - Y + - Y + - Y + * - BCC / RFT / FLY mode [#]_ + - + - + - Y + - Y + - Y + * - Offline build [#]_ + - + - + - + - Y + - + * - Maximum build devices [#]_ + - + - 100 + - 100 + - 200 + - 100 + * - Unlimited local dockers [#]_ + - + - + - + - Y + - + * - Work in CI/CD pipeline [#]_ + - Y + - Y + - + - + - Y - Each obfuscation need online verify license. +.. rubric:: notes - Pyarmor Group +.. [#] Big Script: file size exceeds a certain value. + Mix Str: obfuscating string constant in script +.. [#] RFT Mode: renaming function/class/method/variable in Python scripts + BCC Mode: Transforming some Python functions in scripts to c functions, compile them to machine instructions directly +.. [#] Offline build: the build device need not be online to verify Pyarmor License +.. [#] Maximum devices could install Pyarmor +.. [7] Unlimited local dockers: run docker container in local machine, may be offline or in private network +.. [8] Work in CI/CD pipeline: it need special option to register Pyarmor in CI/CD pipeline - Group license could unlock limitations (1) (2) (3) (4) (5) (7). +.. important:: - Group license supports unlimited dockers which use default bridge network and not highly customized, for Pyarmor Basic or Pyarmor Pro, up to 100 docker runs in 24 hours. + All Pyarmor Licenses are only used to generate the obfuscated scripts in build device - Offline obfuscation, it need not internet connection when obfuscating the scripts. + It need neither install Pyarmor nor verify Pyarmor License to execute obfuscated scripts -**For Pyarmor Basic and Pro License, only Pyarmor License file, serial number of hard disk, Ethernet address, IPv4/IPv6 address, and hostname will be sent to remote server for verification. No any user script will be uploaded to remote server** + The obfuscated scripts are generated by Pyarmor, but they are completely independent of Pyarmor -**Group license is bind to device hardwares, it may not work in any device which hardwares are volatile (docker container is OK only if group license works in docker host)** + You can take them as normal Python scripts, so what the obfuscated scripts do is controlled by you, not by Pyarmor -**Pyarmor License is only allowed to be installed in the build environments to generate the obfuscated scripts, it need not Pyarmor to run the obfuscated scripts, and it's not allowed to install Pyarmor License with the obfuscated scripts in your customer devices** +Terms of Use +============ -Each license has an unique number, the format is ``pyarmor-vax-xxxxxx``, which x stands for a digital. +1. Only use Pyarmor on your own scripts -Each product requires one License No. So any product in global also has an unique number in Pyarmor world. + In any case, even you have purchased Pyarmor License, it's not allowed to obfuscate other scripts of which you haven't property. For example, call pyarmor in your app to obfuscate your customer's scripts, provide obfuscation services based on Pyarmor by website etc. -**License No. and product name will be embedded into obfuscated scripts, all the other user's information, for example, regname, email are not** +2. No profit no license required -If user has many products, and has purchased one license for the first product. The second product could use first product license only if sale income of the second product less than 30x license fees. Once greater than 30x license fees, the second product need purchase its own license. It's same to user's other products. + Free version can be used to obfuscate your scripts which COULD NOT make lot of money for you. Otherwise it need purchase Pyarmor License. -One product in Pyarmor world means a product name and everything that makes up this name. +3. One product one license -It includes all the devices to develop, build, debug, test product. + One product means one kind of product, not one copy of product. For example, Micorsoft Excel is one product, even it's installed on coutless devices -It also includes product current version, history versions and all the future versions. + Each license has an unique number, the format is ``pyarmor-vax-xxxxxx``, which x stands for a digital. -One product may has several variants, each variant name is composed of product name plus feature name. As long as the proportion of the variable part is far less than that of the common part, they're considered as "one product". +4. Pay once -Pyarmor License could be installed in many machines and devices which belong to licensed product. But there is limitation to be used at the same time. + The purchased license works forever with Pyarmor version when this license is purchased, but may not work in future Pyarmor version -In 24 hours only less than 100 devices can use one same Pyarmor Basic or Pro License. Pyarmor License be used means use any feature of Pyarmor in one machine. Running obfuscated scripts generated by Pyarmor is not considered as Pyarmor License be used. +5. Fair use -For Pyarmor Group License, one license allows 100 offline devices to be registered. The allocated device No. is never free, if a device is reinstalled, it need allocate new one. + If you have purchased Pyarmor License for one product, but you have aother product, and the total revenue of the other project is less than 100 x Pyarmor License fee, you could rent this license in your aother product .. seealso:: `Pyarmor EULA`_, :doc:`Using Pyarmor Licenses ` -**What's one product** - -First of all, if not for sale, all the Python scripts are belong to one product "non-profits". - -Pyarmor is one product, it includes: - -* Pyarmor basic, Pyarmor pro, and Pyarmor group -* pyarmor-webui which provides graphics interface for pyarmor. -* the order system of Pyarmor is a Django's app running in cloud-server. This Django's app also belongs to one product Pyarmor. -* the laptop used to develop Pyarmor, the PCs used to test Pyarmor, the cloud-server to serve order system of Pyarmor, all of them belong to one product Pyarmor. -* Pyarmor 7.x, Pyarmor 8.x and Pyarmor 9.x - -Microsoft Office is not one product, because each product in Microsoft Office is functional independence. For example, Microsoft Word and Microsoft Excel belong to Microsoft Office, but they're totally different. - -Microsoft Word is one product, and Microsoft Word 2003,Word 2007 etc. are belong to one product Microsoft word. - -**Hints for Pyarmor licenses** +Privacy +======= -- Basic, Pro and Group license Only work for Python 3.7+ in supported platforms. If using Python 2.7 or prior to 3.7, only Pyarmor old license works. -- If need generate obfuscated scripts offline, only Pyarmor Group or Pyarmor old license works -- Pyarmor Basic and Pro license need internet connection to verify license when obfuscating the scripts, and in 24 hours only 100 different devices are allowed to obfuscate the scripts for each license. -- In 24 hours, if need more than 100 docker containers to obfuscate your scripts, only Pyarmor Group or Pyarmor old license works. -- Pyarmor Basic, Pro and Group licenses don't work for Pyarmor 7. +License No. and product name will be embedded into obfuscated scripts, all the other user's information, for example, regname, email are not -Check device for group license ------------------------------- +For Pyarmor Basic and Pro License, only Pyarmor License file, serial number of hard disk, Ethernet address, IPv4/IPv6 address, and hostname will be sent to remote server for verification. No any user script will be uploaded to remote server -Check one device works for group license by this way: +Technical Support +================= -* First install Pyarmor 8.4.0+ trial version in this device -* Got machine id by the following command:: +License fees only for unlock features, not include technical supports - $ pyarmor reg -g 1 - ... - INFO current machine id is "mc92c9f22c732b482fb485aad31d789f1" - INFO device file has been generated successfully +Users need to learn Pyarmor features and how to use it by themself. Generally Pyarmor Team won't help to debug users' case and teach them how to use Pyarmor -* Reboot this device, check machine id is same or not -* If machine id is same after each reboot, group license works in this device. Otherwise group license doesn't work in this device. +Pyarmor provides comprehensive learning systems, including but not limited to the following ways: -For docker container, please check docker host as above. Only if docker host could work with group license, unlimited docker containers could be run in this docker host, refer to :doc:`how-to/register` section ``run unlimited dockers in offline device`` +- Online documentation +- Checklist and FAQs could fix 90% issues reported to Pyarmor Team +- Discussions in Pyarmor project home +- pyarmor man (coming soon), help command could show each option usage by full examples +- EKE Learning Platform (coming soon), learn Pyarmor concepts by animations for junior users -**If machine id of docker host is changed after reboot, group license doesn't work in any docker container** +Rome was not bulit in a day. Pyarmor Team keeps improving documentation and learning systems according to users feedback to make it easy and effects -Most of physics machine, cloud server or VM like qemu, virtual box, vmware with same disk image work with Group license. If using group in CI pipeline, the default runner doesn't work, but something like `self-host runner`__ may work. +Report bugs and request new features in Pyarmor project home, Email to pyarmor@163.com is only for security and private issues, there may no reply for common technical issues. -__ https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners +Pyarmor team generally will handle submitted issues within 24 hours (working time), but may be extended during holidays or special circumstances -License features ----------------- - -.. table:: Table-1. License Features - :widths: auto - - =================== ======== ======== ========= ======== ============== - Features Trial Basic Pro Group Remark - =================== ======== ======== ========= ======== ============== - Basic Obfuscation Y Y Y Y [5]_ - Expired Script Y Y Y Y [6]_ - Bind Device Y Y Y Y [7]_ - JIT Protection Y Y Y Y [8]_ - Assert Protection Y Y Y Y [9]_ - Themida Protection Y Y Y Y [10]_ - Big Script No Y Y Y [1]_ - Mix Str No Y Y Y [2]_ - obf-code > 1 No Y Y Y [11]_ - RFT MODE No No Y Y [3]_ - BCC MODE No No Y Y [4]_ - Unlimited dockers Y No No Y [12]_ - =================== ======== ======== ========= ======== ============== - -.. rubric:: notes - -.. [1] Big Script means file size exceeds a certain value. -.. [2] Mix Str: obfuscating string constant in script -.. [3] RFT Mode: renaming function/class/method/variable in Python scripts -.. [4] BCC Mode: Transforming some Python functions in scripts to c functions, compile them to machine instructions directly -.. [5] Basic Obfuscation: obfuscating the scripts by default options -.. [6] Expired Script: obfuscated scripts has expired date -.. [7] Bind Device: obfuscated scripts only run in specified devices -.. [8] JIT Protection: processing some sensitive data by runtime generated binary code -.. [9] Assert Protection: preventing others from hacking obfuscated scripts -.. [10] Themida Protection: using Themida to protect Windows dlls -.. [11] ``--obf-code=2`` is new in Pyarmor 8.2 -.. [12] This feature is introduced in Pyarmor 8.3, group license supports unlimited dockers, basic and pro licenses only allow 100 runs one day. Also check above section ``Check device for group license`` +Pyarmor Team doesn't provide any instant technical support by telphone or other similar tools. Purchasing license ================== @@ -180,42 +170,8 @@ Open `Pyarmor Shopping Cart`_ in any web browser: https://jondy.github.io/paypal/index.html -In the shopping cart, select License Type and complete the payment online. - -Please fill reg-name with personal or company name when placing order. - -.. list-table:: Table-2. License Prices - :header-rows: 1 - - * - License Type - - Net Price($) - - Remark - * - Basic - - 52 - - - * - Pro - - 89 - - - * - Group - - 158 - - - -An activation file named ``pyarmor-regcode-xxxx.txt`` will be sent by email immediately after payment is completed successfully. - -Following the guide in activation file to take the purchased license effects, or check :doc:`../how-to/register` - -**There are no additional license fees, apart from the cost of the license. And it only needs to be paid once, not periodically** - -**The license is valid forever in Pyarmor version when purchasing it, but may not work in Pyarmor future versions** - -.. important:: - - License fee is only for unlocking features, not for technical support. - - Refer to :ref:`asking questions` for technical support. - Refund policy -------------- +============= If activation file isn't used, and purchasing date is in 30 days, refund is acceptable. @@ -229,49 +185,35 @@ If activation file isn't used, and purchasing date is in 30 days, refund is acce - For other cases, email to pyarmor@163.com Out of 30 days, or activation file has been used, refund request will be rejected. - .. _FindMyOrder page: https://www.findmyorder.com/store?Action=DisplayEmailCustomerServicePage&Env=BASE&Locale=en_US&SiteID=findmyor - -.. _upgrading old license: - -Upgrading old license -===================== - -Not all the old license could be upgraded to latest version. - -The old license could be upgraded to Pyarmor Basic freely only if it matches these conditions: - -* Following new `Pyarmor EULA`_ -* The license no. starts with ``pyarmor-vax-`` -* The original activation file ``pyarmor-regcode-xxxx.txt`` exists and isn't used more than 100 times -* The old license is purchased before June 1, 2023. In principle, the old license purchased after Pyarmor 8 is available could not be upgraded to new license. -If failed to upgrade the old license, please purchase new license to use Pyarmor latest version. +.. _FindMyOrder page: https://www.findmyorder.com/store?Action=DisplayEmailCustomerServicePage&Env=BASE&Locale=en_US&SiteID=findmyor -The old license can't be upgraded to Pyarmor Pro and Group. +Appendix +======== -Upgrading old license to Pyarmor Basic --------------------------------------- +What Is One Product +------------------- -First find the activation file ``pyarmor-regcode-xxxx.txt``, which is sent to registration email when purchasing the license. - -Next install Pyarmor 8.2+, according to new `EULA of Pyarmor`_, each license is only for one product. - -Assume this license will be used to obfuscate product ``XXX``, run this command:: +First of all, if not for sale, all the Python scripts are belong to one product "non-profits". - $ pyarmor reg -u -p "XXX" pyarmor-regcode-xxxx.txt +One product in Pyarmor world means a product name and everything that makes up this name. -Check the upgraded license information:: +It includes all the devices to develop, build, debug, test product. - $ pyarmor -v +It also includes product current version, history versions and all the future versions. -After upgrade successfully, do not use activation file ``pyarmor-regcode-xxxx.txt`` again, it's invalid now. A new :term:`registration file` like :file:`pyarmor-regfile-xxxx.zip` will be generated at the same time. +One product may has several variants, each variant name is composed of product name plus feature name. As long as the proportion of the variable part is far less than that of the common part, they're considered as "one product". -In other devices using this new :term:`registration file` to register Pyarmor by this command:: +Pyarmor is one product, it includes: - $ pyarmor reg pyarmor-regfile-xxxx.zip +* Pyarmor basic, Pyarmor pro, and Pyarmor group +* pyarmor-webui which provides graphics interface for pyarmor. +* the order system of Pyarmor is a Django's app running in cloud-server. This Django's app also belongs to one product Pyarmor. +* the laptop used to develop Pyarmor, the PCs used to test Pyarmor, the cloud-server to serve order system of Pyarmor, all of them belong to one product Pyarmor. +* Pyarmor 7.x, Pyarmor 8.x and Pyarmor 9.x -After successful registration, all obfuscations will automatically apply this license, and each obfuscation requires online license verification. +Microsoft Office is not one product, because each product in Microsoft Office is functional independence. For example, Microsoft Word and Microsoft Excel belong to Microsoft Office, but they're totally different. -If old license is used by many products (mainly old personal license), only one product could be used after upgrading. For the others, it need purchase new license. +Microsoft Word is one product, and Microsoft Word 2003,Word 2007 etc. are belong to one product Microsoft word. .. include:: _common_definitions.txt From 3e125feb09b1925dda32173a793c5e731e06124e Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 7 Oct 2024 18:17:12 +0800 Subject: [PATCH 007/337] Fix format --- docs/licenses.rst | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index 80134716..b39a3988 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -17,26 +17,18 @@ Pyarmor is published in `PyPI`_, free trial version never expires. Try it by the $ pyarmor gen foo.py $ python dist/foo.py -There are some limitations in free version, for example, can't obfuscate big scripts etc. These limitations can be unlocked by different license types - -License types -============= - -.. versionadded:: 9.0 - Pyarmor CI License - -Pyarmor has 4 kind of licenses: +There are some limitations in free version, for example, can't obfuscate big scripts etc. These limitations can be unlocked by different license types. Pyarmor has 4 kind of licenses: - Basic -- Pro -- Group -- CI +- Pro: Irreversible Obfuscation +- Group: Offline build +- CI: for CI/CD pipeline, new in v9.0 .. list-table:: Table-1. Compare Different Licenses :widths: 40 12 12 12 12 12 :header-rows: 1 :stub-columns: 1 - :align: center + :align: left center center center center center * - Feature - Free @@ -83,14 +75,16 @@ Pyarmor has 4 kind of licenses: .. rubric:: notes -.. [#] Big Script: file size exceeds a certain value. - Mix Str: obfuscating string constant in script -.. [#] RFT Mode: renaming function/class/method/variable in Python scripts - BCC Mode: Transforming some Python functions in scripts to c functions, compile them to machine instructions directly +.. [#] + - Big Script: file size exceeds a certain value. + - Mix Str: obfuscating string constant in script +.. [#] + - RFT Mode: renaming function/class/method/variable in Python scripts + - BCC Mode: Transforming some Python functions in scripts to c functions, compile them to machine instructions directly .. [#] Offline build: the build device need not be online to verify Pyarmor License .. [#] Maximum devices could install Pyarmor -.. [7] Unlimited local dockers: run docker container in local machine, may be offline or in private network -.. [8] Work in CI/CD pipeline: it need special option to register Pyarmor in CI/CD pipeline +.. [#] Unlimited local dockers: run docker container in local machine, may be offline or in private network +.. [#] Work in CI/CD pipeline: it need special option to register Pyarmor in CI/CD pipeline .. important:: From 2dfc7ef2ef2837811f6765171a20a9695d5e431b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 7 Oct 2024 18:25:21 +0800 Subject: [PATCH 008/337] Fix format error --- docs/how-to/ci.rst | 15 --------------- docs/licenses.rst | 1 - 2 files changed, 16 deletions(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index dd338a68..eb310853 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -5,7 +5,6 @@ ============================== .. deprecated:: 9.0 - Use Pyarmor Basic or CI License directly instead. Pyarmor also could be used in CI/CD pipeline by this way: @@ -57,18 +56,4 @@ The first runner will obfuscate the scripts and store them into another branch. For all the other runners, they need not install Pyarmor, just checkout branch `master-obf`, and work as before. -.. _using pyarmor ci quota: - -Using Pyarmor CI Quota -====================== - -.. versionremoved:: 9.0 - This feature has been removed. - -Pyarmor CI Quota is one experimental solution for this issue: - -- Basic/Pro license only allow 100 runs in CI/CD pipeline in 24 hours - -Each ci quota allows 2,000 extra runs after 100 runs in 24 hours. - .. include:: ../_common_definitions.txt diff --git a/docs/licenses.rst b/docs/licenses.rst index b39a3988..28ceebcc 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -28,7 +28,6 @@ There are some limitations in free version, for example, can't obfuscate big scr :widths: 40 12 12 12 12 12 :header-rows: 1 :stub-columns: 1 - :align: left center center center center center * - Feature - Free From f996a95863ada3fb9024b9d5677a7fbd1eeed47e Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 7 Oct 2024 18:56:00 +0800 Subject: [PATCH 009/337] Refine doc --- docs/how-to/ci.rst | 2 ++ docs/licenses.rst | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index eb310853..8545e757 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -6,6 +6,8 @@ .. deprecated:: 9.0 + Refer to :ref:`Using Pyarmor in CI Pipeline` + Pyarmor also could be used in CI/CD pipeline by this way: - First obfuscate the scripts by a few runner and store them to another branch like `master-obf` diff --git a/docs/licenses.rst b/docs/licenses.rst index 28ceebcc..51b13778 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -120,7 +120,9 @@ Terms of Use If you have purchased Pyarmor License for one product, but you have aother product, and the total revenue of the other project is less than 100 x Pyarmor License fee, you could rent this license in your aother product -.. seealso:: `Pyarmor EULA`_, :doc:`Using Pyarmor Licenses ` +In details check `Pyarmor EULA`_ + +.. seealso:: :doc:`Using Pyarmor Licenses ` Privacy ======= @@ -184,7 +186,7 @@ Out of 30 days, or activation file has been used, refund request will be rejecte Appendix ======== -What Is One Product +What is one product ------------------- First of all, if not for sale, all the Python scripts are belong to one product "non-profits". From 0ee989bfb87312470e6aea07ed1d280617a778e3 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 7 Oct 2024 20:07:27 +0800 Subject: [PATCH 010/337] Refine doc --- docs/how-to/register.rst | 73 +++++++++++++++------------------------- docs/licenses.rst | 2 +- 2 files changed, 29 insertions(+), 46 deletions(-) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index f574be58..bfa05f02 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -79,38 +79,6 @@ Check the registration information:: After successful registration, all obfuscations will automatically apply this license, and each obfuscation requires online license verification. -Registering in Docker or CI pipeline ------------------------------------- - -.. versionchanged:: 9.0 - The Pyarmor Pro License couldn't be used in CI/CD pipeline since 9.0, use Pyarmor CI License instead. - -Refer to :ref:`Using Pyarmor in CI Pipeline` - -.. _Check Device For Group License: - -Check Device For Group License -============================== - -Check one device works for group license by this way: - -* First install Pyarmor 8.4.0+ trial version in this device -* Got machine id by the following command:: - - $ pyarmor reg -g 1 - ... - INFO current machine id is "mc92c9f22c732b482fb485aad31d789f1" - INFO device file has been generated successfully - -* Reboot this device, check machine id is same or not -* If machine id is same after each reboot, group license works in this device. Otherwise group license doesn't work in this device. - -For docker container, please check docker host as above. Only if docker host could work with group license, unlimited docker containers could be run in this docker host, refer to :doc:`how-to/register` section ``run unlimited dockers in offline device`` - -**If machine id of docker host is changed after reboot, group license doesn't work in any docker container** - -Most of physics machine, cloud server or VM like qemu, virtual box, vmware with same disk image work with Group license. Most of runners in CI/CD pipeline could not use Group License. - Using group license =================== @@ -132,8 +100,6 @@ Basic steps: .. [#] Pyarmor will review group license manually and enable it in 24 hours since activation file is sent. .. [#] The device registration file is bind to specified device, each device has its own device regfile -__ https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners - Initial registration -------------------- @@ -399,24 +365,17 @@ Anyway, please configure the docker host/container network so that `pyarmor-auth If run `pyarmor-auth` in Linux VM or WSL, please check group license could work in Linux VM or WSL. It may need generate new device regfile for Linux VM or WSL. -Using group license in CI pipeline ----------------------------------- - -.. deprecated:: 9.0 - Use Pyarmor CI License instead, refer to :ref:`Using Pyarmor in CI Pipeline` - -Pyarmor Group License could not be used in CI pipeline generally. - .. _using pyarmor in ci pipeline: Using Pyarmor in CI Pipeline ============================= -.. versionadded:: 9.0 +.. versionchanged:: 9.0 + Pyarmor Pro License couldn't be used in CI/CD pipeline since 9.0 -For free version, it's enough to install pyarmor by `pip install pyarmor` in the pipeline. +For free version, just install pyarmor by `pip install pyarmor` in the pipeline. -Pyarmor Pro License and Pyarmor Group License couldn't be used in CI/CD pipeline. +Since Pyarmor 9.0, Pyarmor Pro License and Pyarmor Group License couldn't be used in CI/CD pipeline. Pyarmor Basic and CI License could be used in CI/CD pipeline by this way @@ -471,6 +430,30 @@ But in the following versions something is changed - Finally, replace old one with new one +.. _check device for group license: + +Check Device For Group License +============================== + +Check one device works for group license by this way: + +* First install Pyarmor 8.4.0+ trial version in this device +* Got machine id by the following command:: + + $ pyarmor reg -g 1 + ... + INFO current machine id is "mc92c9f22c732b482fb485aad31d789f1" + INFO device file has been generated successfully + +* Reboot this device, check machine id is same or not +* If machine id is same after each reboot, group license works in this device. Otherwise group license doesn't work in this device. + +For docker container, please check docker host as above. Only if docker host could work with group license, unlimited docker containers could be run in this docker host, refer to :doc:`how-to/register` section ``run unlimited dockers in offline device`` + +**If machine id of docker host is changed after reboot, group license doesn't work in any docker container** + +Most of physics machine, cloud server or VM like qemu, virtual box, vmware with same disk image work with Group license. Most of runners in CI/CD pipeline could not use Group License. + .. _upgrading old license: Upgrading old license diff --git a/docs/licenses.rst b/docs/licenses.rst index 51b13778..34004d7a 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -72,7 +72,7 @@ There are some limitations in free version, for example, can't obfuscate big scr - - Y -.. rubric:: notes +.. rubric:: Notes .. [#] - Big Script: file size exceeds a certain value. From 22c4081b2114c58a8cedbe7910b13a233cd55093 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 8 Oct 2024 16:51:54 +0800 Subject: [PATCH 011/337] Doc CI License is year subscription --- docs/licenses.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index 34004d7a..6d8402ef 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -114,7 +114,7 @@ Terms of Use 4. Pay once - The purchased license works forever with Pyarmor version when this license is purchased, but may not work in future Pyarmor version + Except CI License, all the other licenses work forever with Pyarmor version when this license is purchased, but **may not work in future Pyarmor versions** 5. Fair use From 5f3289037ac731d978caecd59f11a452a1049395 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 8 Oct 2024 18:17:36 +0800 Subject: [PATCH 012/337] Refine doc --- docs/licenses.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index 6d8402ef..58abdc4e 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -58,7 +58,7 @@ There are some limitations in free version, for example, can't obfuscate big scr - 100 - 100 - 200 - - 100 + - 20 * - Unlimited local dockers [#]_ - - From 746d2ed2d1bf09bec61b103c911837b25fb352b5 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 8 Oct 2024 22:08:54 +0800 Subject: [PATCH 013/337] Fix bug --- src/cli/core/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index 8c42d2fa..f3c2c619 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -50,7 +50,7 @@ def check_and_install_prebuilt_package(): platname = os.getenv( 'PYARMOR_PLATFORM', - '.'.join(format_platform(system().lower(), machine().lower()))) + format_platform(system().lower(), machine().lower())) platname = map_platform(platname) if platname not in PLATFORM_NAMES: raise RuntimeError('"%s" is still not supported by Pyarmor' % platname) From d3b2600bf69696fff5a45b82331acf29f764a6ce Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 8 Oct 2024 22:12:51 +0800 Subject: [PATCH 014/337] Doc Apple Silcon only works for Python 3.9+ --- docs/reference/environments.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/environments.rst b/docs/reference/environments.rst index f981ef4d..5653b207 100644 --- a/docs/reference/environments.rst +++ b/docs/reference/environments.rst @@ -36,7 +36,7 @@ Supported platforms :widths: auto =================== ============ ======== ======= ============ ========= ======= ======= - OS Windows Apple Linux [#]_ + OS Windows Apple [#]_ Linux [#]_ ------------------- ------------ ----------------- ----------------------------------------- Arch x86/x86_64 x86_64 arm64 x86/x86_64 aarch64 armv7 armv6 =================== ============ ======== ======= ============ ========= ======= ======= @@ -78,6 +78,7 @@ Supported platforms .. rubric:: notes .. [#] ``N/y`` means not yet now, but will be supported in future. +.. [#] Apple Silcon only supports Python 3.9+ .. [#] This Linux is built with glibc .. [#] pyarmor-7 also supports more linux arches, refer to `Pyarmor 7.x platforms`__. .. [#] These platforms are introduced in Pyarmor 8.3 From 60919118fb5c5cf751d803924e39c84d35a4192a Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 9 Oct 2024 00:07:51 +0800 Subject: [PATCH 015/337] Refine code --- src/cli/register.py | 135 ++++++++++++++++++++++++++++---------------- 1 file changed, 87 insertions(+), 48 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index c5083957..24a487be 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -148,6 +148,50 @@ def parse_keyfile(self, filename): raise CliError('no registration code found in %s' % filename) + def _register_offline_license(self, fzip, namelist): + logger.info('machine id in group license: %s', ', '.join( + [x[7:] for x in namelist if x.startswith('tokens')])) + for idver in MACHFLAGS: + machid = self._get_machine_id(idver).decode('utf-8') + logger.debug('got machine id: %s', machid) + name = '/'.join(['tokens', machid]) + if name in namelist: + logger.info('this machine id matchs group license') + break + else: + logger.info('no machine id matchs this group license') + logger.info('take this machine as docker container, and ' + 'connect to docker host for authentication...') + mlist = self._get_docker_hostname() + if not mlist: + logger.info( + 'could not get docker host machine id\n%s', + '\n'.join([ + '', + 'if this machine is docker container, please ' + 'run command `pyarmor-auth` in docker host, ' + 'and try it again', '', + 'otherwise please generate new group ' + 'device license for this machine', '', + 'more information please check section ' + '"using group license" in documentation ' + '"how-to register" guide', '' + ])) + raise CliError('this group device license is not for ' + 'this machine') + for machid in mlist: + hostname = '/'.join(['tokens', machid]) + if hostname in namelist: + name = hostname + break + else: + logger.debug('docker host machine ids: %s', mlist) + raise CliError('this group device license is not for ' + 'this docker host') + + logger.debug('extracting %s', name) + self.ctx.save_group_token(fzip.read(name)) + def register_regfile(self, regfile, clean=True): from zipfile import ZipFile @@ -163,56 +207,19 @@ def register_regfile(self, regfile, clean=True): f.extract(item, path=path) namelist = f.namelist() if 'group.tokens' in namelist: - logger.info('machine id in group license: %s', ', '.join([ - x[7:] for x in namelist if x.startswith('tokens') - ])) - for idver in MACHFLAGS: - machid = self._get_machine_id(idver).decode('utf-8') - logger.info('got machine id: %s', machid) - name = '/'.join(['tokens', machid]) - if name in namelist: - logger.info('this machine id matchs group license') - break - else: - logger.info('no machine id matchs this group license') - logger.info('take this machine as docker container, and ' - 'connect to docker host for authentication...') - mlist = self._get_docker_hostname() - if not mlist: - logger.info( - 'could not get docker host machine id\n%s', - '\n'.join([ - '', - 'if this machine is docker container, please ' - 'run command `pyarmor-auth` in docker host, ' - 'and try it again', '', - 'otherwise please generate new group ' - 'device license for this machine', '', - 'more information please check section ' - '"using group license" in documentation ' - '"how-to register" guide', '' - ])) - raise CliError('this group device license is not for ' - 'this machine') - for machid in mlist: - hostname = '/'.join(['tokens', machid]) - if hostname in namelist: - name = hostname - break - else: - logger.debug('docker host machine ids: %s', mlist) - raise CliError('this group device license is not for ' - 'this docker host') + self._register_offline_license(f, namelist) + elif 'ci.token' in namelist: + name = 'ci.token' logger.debug('extracting %s', name) - self.ctx.save_group_token(f.read(name)) - return - if 'group.info' in namelist: - logger.info('refer to http://pyarmor.readthedocs.io/en/stable/how-to/register.html' - '#using-group-license') + self.ctx.save_token(f.read(name)) + elif 'group.info' in namelist: + docurl = 'http://pyarmor.readthedocs.io/en/stable' + logger.info('refer to %s/how-to/register.html' + '#using-group-license', docurl) raise CliError('wrong usage for group license') - - logger.info('update license token') - self.update_token() + else: + logger.info('update license token') + self.update_token() def _get_docker_hostname(self): try: @@ -625,3 +632,35 @@ def register_group_device(self, regfile, devid, rev=1): logger.info('please copy deivce regfile to offline device and run') logger.info(' pyarmor reg %s', filename) + + def register_ci_license(self, regfile, host, rev=1, age=1): + from zipfile import ZipFile + logger.info('start to generate ci/cd license') + + with ZipFile(regfile, 'r') as f: + licdata = f.read('license.lic') + capsule = f.read('.pyarmor_capsule.zip') + cidata = json_loads(f.read('ci.info')) + + logger.info('send request to server') + url = self.regurl('/'.join(['ci', cidata['ucode']])) + paras = ( + ('rev', str(rev)), + ('age', str(age)), + ('cindex', str(cidata['cindex'])), + ('source', ''), + ) + url += '&'.join(['='.join(x) for x in paras]) + logger.debug('url: %s', url) + + res = self._send_request(url) + filename = self._handle_response(res) + with open(filename, 'rb') as f: + data = f.read() + + with ZipFile(filename, 'w') as f: + f.writestr('license.lic', licdata) + f.writestr('.pyarmor_capsule.zip', capsule) + f.writestr('ci.token', data) + + logger.info('generate ci regfile %s successfully', filename) From 81f822a2eaaef49c5eb27cc7b953148acba8352d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 9 Oct 2024 07:06:23 +0800 Subject: [PATCH 016/337] Refine doc --- docs/licenses.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index 58abdc4e..ece317d2 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -143,8 +143,8 @@ Pyarmor provides comprehensive learning systems, including but not limited to th - Online documentation - Checklist and FAQs could fix 90% issues reported to Pyarmor Team - Discussions in Pyarmor project home -- pyarmor man (coming soon), help command could show each option usage by full examples -- EKE Learning Platform (coming soon), learn Pyarmor concepts by animations for junior users +- Full examples to show each option usage and common cases by command ``pyarmor man`` (coming soon) +- Learn Pyarmor concepts by figure and animations in EKE Learning Platform (coming soon) Rome was not bulit in a day. Pyarmor Team keeps improving documentation and learning systems according to users feedback to make it easy and effects From 0e80e11d06aa89712fb6e0c20e2fc1c9260a8531 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 9 Oct 2024 07:21:31 +0800 Subject: [PATCH 017/337] Update release plan --- ReleasePlan.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/ReleasePlan.md b/ReleasePlan.md index db58efea..cd985e7d 100755 --- a/ReleasePlan.md +++ b/ReleasePlan.md @@ -54,20 +54,28 @@ Status: released (2023-10-18) **8.5** -Status: first released at 2024-03-08, unstable, still developing +Status: released (2024-03-08) - Expert users could write their own python code in pyarmor_runtime initialization -- Pyarmor.man package with web-ui to help users learning, using Pyarmor, and quickly find solutions when something is wrong - New plugin "post_script" called after each script has been obfuscated - Runtime key could be obfuscated - No patch `pyarmor_runtime.so` for Apple Silcon so Windows users could build obfuscated scripts for Apple Silcon -**8.6** +**9.0** -Status: not start +Status: developing, released before Nov. 1 as plan +- Pyarmor.man package with web-ui to help users learning, using Pyarmor, and quickly find solutions when something is wrong +- Add special Pyarmor CI License for CI/CD pipeline - Full test + +**9.1** + +Status: not start + +- Support Python 3.13 - Refine RFT mode to improve performance for big scripts +- FLY mode, a simple RFT mode, high performace goal, used by webapi like Djanjo - Convert current unsupported node types as far as possible for BCC mode - Expert users could customize C functions generated by BCC mode - BCC mode support cross-platform (may not if too complex) From 5bccf81f9f2936acebf3cd52cf56caca22ec119a Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 9 Oct 2024 08:41:04 +0800 Subject: [PATCH 018/337] Add register_ci_license --- src/cli/register.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index 24a487be..150a1094 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -633,34 +633,26 @@ def register_group_device(self, regfile, devid, rev=1): logger.info('please copy deivce regfile to offline device and run') logger.info(' pyarmor reg %s', filename) - def register_ci_license(self, regfile, host, rev=1, age=1): - from zipfile import ZipFile - logger.info('start to generate ci/cd license') + def register_ci_license(self, keyfile, host, rev=1, age=1): + logger.info('generate ci license from "%s"', keyfile) + reginfo = self.parse_keyfile(keyfile) - with ZipFile(regfile, 'r') as f: - licdata = f.read('license.lic') - capsule = f.read('.pyarmor_capsule.zip') - cidata = json_loads(f.read('ci.info')) + if len(reginfo[1]) != 192: + raise CliError('invalid activation file "%s"', keyfile) - logger.info('send request to server') - url = self.regurl('/'.join(['ci', cidata['ucode']])) + logger.info('check CI server "%s"', host) + + url = self.regurl('ci/%s' % reginfo[1]) paras = ( ('rev', str(rev)), ('age', str(age)), - ('cindex', str(cidata['cindex'])), - ('source', ''), ) url += '&'.join(['='.join(x) for x in paras]) logger.debug('url: %s', url) + logger.info('send request to server') res = self._send_request(url) - filename = self._handle_response(res) - with open(filename, 'rb') as f: - data = f.read() - - with ZipFile(filename, 'w') as f: - f.writestr('license.lic', licdata) - f.writestr('.pyarmor_capsule.zip', capsule) - f.writestr('ci.token', data) - logger.info('generate ci regfile %s successfully', filename) + logger.info('handle response') + cifile = self._handle_response(res) + logger.info('generate ci regfile %s successfully', cifile) From 27289ae121abd4c11af0bd5200d7ddb6bbee7ee8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 13 Oct 2024 13:44:59 +0800 Subject: [PATCH 019/337] Add note for rft-auto-include mode --- docs/topic/rftmode.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topic/rftmode.rst b/docs/topic/rftmode.rst index bd5bb4f5..415d2a0d 100644 --- a/docs/topic/rftmode.rst +++ b/docs/topic/rftmode.rst @@ -56,6 +56,8 @@ There are 2 methods for RFT mode to handle name in the attribute chains which do This method first search all the functions, classes and methods in the scripts, add them to include table, and transform all of them. If same name is used in attribute chains, but can't make sure its type, leave attribute name as it is. + Note that in rft-auto-include mode, local variables will not be touched, but they're renamed in next obfuscation process, unless you explicitly disable it by `pyarmor cfg mix_localnames=0`. + For a simple script, Pyarmor could transform the script automatically. But for a complex script, it may raise name binding error. For example:: $ python dist/foo.py From cfb8bfb6da8bd330edfad3c37b290cf2e25c179d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 16 Oct 2024 18:51:20 +0800 Subject: [PATCH 020/337] Support CI License --- src/cli/__main__.py | 9 ++++++ src/cli/context.py | 1 + src/cli/register.py | 77 +++++++++++++++++++++++++++++++++++++-------- 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index e282432d..359c6d91 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -308,6 +308,11 @@ def cmd_reg(ctx, args): regsvr.register_group_device(regfile, args.device) logger.info('The device regfile has been generated successfully') + elif args.ci: + regsvr = WebRegister(ctx) + regsvr.check_request_interval() + regsvr.register_ci_license(regfile) + elif regfile.endswith('.zip'): reg = Register(ctx) logger.info('register "%s"', regfile) @@ -627,6 +632,10 @@ def reg_parser(subparsers): '-y', '--confirm', action='store_true', help=argparse.SUPPRESS ) + cparser.add_argument( + '-C', '--CI', action='store_true', dest='ci', + help='request license regfile for CI pipeline' + ) cparser.add_argument( 'regfile', nargs='?', metavar='FILE', diff --git a/src/cli/context.py b/src/cli/context.py index 068ad359..cf4f7fd9 100644 --- a/src/cli/context.py +++ b/src/cli/context.py @@ -304,6 +304,7 @@ def version_info(self, verbose=3): lictype = 'basic' if licinfo['features'] == 1 else \ 'pro' if licinfo['features'] == 7 else \ 'group' if licinfo['features'] == 15 else \ + 'ci' if licinfo['features'] == 23 else \ 'trial' if licinfo['token'] == 0 else 'unknown' verinfo = ['%s (%s)' % (rev, lictype)] diff --git a/src/cli/register.py b/src/cli/register.py index 150a1094..5afbc4f0 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -124,6 +124,7 @@ def _license_type(self, info): return 'basic' if info['features'] == 1 else \ 'pro' if info['features'] == 7 else \ 'group' if info['features'] == 15 else \ + 'ci' if info['features'] == 23 else \ 'trial' if info['token'] == 0 else 'unknown' def _license_to(self, info): @@ -326,9 +327,11 @@ def __str__(self): bccmode = info['features'] & 2 rftmode = info['features'] & 4 + cidmode = info.get('machine', '').startswith('CITK') advanced = [ fmt % ('BCC Mode', 'Yes' if bccmode else 'No'), fmt % ('RFT Mode', 'Yes' if rftmode else 'No'), + fmt % ('CI/CD Mode', 'Yes' if cidmode else 'No'), ] if lictype == 'trial': self.notes.append('* Can\'t obfuscate big script and mix str') @@ -338,6 +341,9 @@ def __str__(self): elif lictype == 'group': self.notes.append('* Offline obfuscation') + if info['note']: + self.notes.extend(info['note'].splitlines()) + lines.append(Template(self.__str__.__doc__).substitute( advanced='\n'.join(advanced), notes='\n'.join(self.notes), @@ -517,6 +523,8 @@ def register(self, keyfile, product, upgrade=False, group=False): reginfo = self.parse_keyfile(keyfile) url = self.regurl(reginfo[1], product=product) + # Request license file with extra info by rev 2 + url += '&rev=2' if upgrade: url += '&upgrade_to_basic=1' logger.debug('url: %s', url) @@ -554,6 +562,12 @@ def _handle_response(self, res): with open(filename, 'wb') as f: f.write(data[n:]) self._write_group_info(filename, data[:n]) + elif data.startswith(b'REGINFO:'): + i = len(b'REGINFO:') + n = data.find(b'}', i) + 1 + with open(filename, 'wb') as f: + f.write(data[n:]) + self._write_reg_info(filename, data[i:n]) else: with open(filename, 'wb') as f: f.write(data) @@ -564,6 +578,12 @@ def _handle_response(self, res): raise CliError('no response from license server') + def _write_reg_info(self, filename, data): + from zipfile import ZipFile + logger.info('write reg information') + with ZipFile(filename, 'a') as f: + f.writestr('reg.info', data) + def _write_group_info(self, filename, data): from zipfile import ZipFile logger.info('write group information') @@ -633,20 +653,29 @@ def register_group_device(self, regfile, devid, rev=1): logger.info('please copy deivce regfile to offline device and run') logger.info(' pyarmor reg %s', filename) - def register_ci_license(self, keyfile, host, rev=1, age=1): - logger.info('generate ci license from "%s"', keyfile) - reginfo = self.parse_keyfile(keyfile) - - if len(reginfo[1]) != 192: - raise CliError('invalid activation file "%s"', keyfile) + def _write_ci_info(self, filename, data): + from zipfile import ZipFile + logger.info('write ci information') + with ZipFile(filename, 'a') as f: + f.writestr('ci.token', data) - logger.info('check CI server "%s"', host) + def register_ci_license(self, regfile, rev=2): + logger.info('request ci regfile by "%s"', regfile) + from zipfile import ZipFile - url = self.regurl('ci/%s' % reginfo[1]) - paras = ( - ('rev', str(rev)), - ('age', str(age)), - ) + with ZipFile(regfile, 'r') as f: + if 'reg.info' not in f.namelist(): + logger.error('missing reg.info in regfile') + raise CliError('can not request CI license') + reginfo = json_loads(f.read('reg.info')) + + ucode = reginfo['ucode'] + rcode = reginfo['rcode'] + if len(ucode) != 192: + raise CliError('invalid registration file "%s"', regfile) + + url = self.regurl('ci/%s' % ucode) + paras = (('rev', str(rev)),) url += '&'.join(['='.join(x) for x in paras]) logger.debug('url: %s', url) @@ -654,5 +683,27 @@ def register_ci_license(self, keyfile, host, rev=1, age=1): res = self._send_request(url) logger.info('handle response') - cifile = self._handle_response(res) + if res is None: + raise CliError('no response from license server') + + elif res.code != 200: + raise CliError(res.read().decode('utf-8')) + + data = res.read() + if not data.startswith(b'CITOKEN:'): + raise CliError('wrong server data: "%s"' % data) + + i = len(b'CITOKEN:') + 2 + n = data[i-2] + (data[i-1] << 8) + token = data[i:i+n] + + rn = rcode[-6:].lstrip('0') + cifile = 'pyarmor-ci-regfile-%s.zip' % rn + + with ZipFile(regfile, 'r') as src: + with ZipFile(cifile, 'w') as dst: + for x in src.namelist(): + dst.writestr(x, src.read(x)) + dst.writestr('ci.token', token) + logger.info('generate ci regfile %s successfully', cifile) From 1a5d1e6f56f3169fb77c44974a844440335f23fe Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 16 Oct 2024 22:13:32 +0800 Subject: [PATCH 021/337] Refine activation process --- src/cli/__main__.py | 2 +- src/cli/register.py | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 359c6d91..b9f218ff 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -327,7 +327,7 @@ def cmd_reg(ctx, args): if args.confirm: from time import sleep sleep(1.0) - elif input(msg + prompt) not in ('y', 'yes'): + elif input(msg + prompt) not in ('y', 'yes', 'Y', 'Yes'): logger.info('abort registration') return # Free upgrade to Pyarmor Basic diff --git a/src/cli/register.py b/src/cli/register.py index 5afbc4f0..46f5101d 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -533,23 +533,33 @@ def register(self, keyfile, product, upgrade=False, group=False): res = self._send_request(url) regfile = self._handle_response(res) + logger.info('') + logger.info('the registration file "%s" has been generated', regfile) + logger.info('this license has been activated sucessfully') + notes = [ '* Please backup regfile "%s" carefully, and ' - 'use this file for subsequent registration' % regfile, + 'use this file for next any registration' % regfile, '* Do not use "%s" again' % os.path.basename(keyfile), ] if group: - logger.info('This group license has been activated sucessfully') notes.append('* Please check `pyarmor reg` in Man page for ' 'how to register Pyarmor on offline device') - else: + logger.info('\n\nImport Notes:\n%s\n', '\n'.join(notes)) + return + + notes.append('') + notes.append('Next register Pyarmor in any device by this command:') + notes.append('\tpyarmor reg %s' % regfile) + logger.info('\n\nImport Notes:\n%s\n', '\n'.join(notes)) + + prompt = 'Do you want register Pyarmor in this machine? (Y/n) ' + choice = input(prompt).lower()[:1] + if choice == 'y': logger.info('register "%s"', regfile) self.register_regfile(regfile) - logger.info('This license code has been %s successfully', - 'upgraded' if upgrade else 'activated') - - logger.info('Import Notes:\n\n%s\n', '\n'.join(notes)) + logger.info('This license registration information:\n\n%s', self) def _handle_response(self, res): if res and res.code == 200: From 0866363673ed30e1a61debbd6eed3b2fc6fbbce4 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 16 Oct 2024 22:33:22 +0800 Subject: [PATCH 022/337] Refine prompt --- src/cli/register.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index 46f5101d..f100fdcb 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -550,8 +550,10 @@ def register(self, keyfile, product, upgrade=False, group=False): return notes.append('') - notes.append('Next register Pyarmor in any device by this command:') + notes.append('Next register Pyarmor in any device by this command' + '(except docker and CI/CD pipeline):') notes.append('\tpyarmor reg %s' % regfile) + notes.append('') logger.info('\n\nImport Notes:\n%s\n', '\n'.join(notes)) prompt = 'Do you want register Pyarmor in this machine? (Y/n) ' From d2a2c2aac2fde8a0164894d290e5090ffd27b2a0 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 17 Oct 2024 10:21:10 +0800 Subject: [PATCH 023/337] change core version to 7.6.0 --- setup.py | 2 +- src/cli/core/__init__.py | 2 +- src/cli/default.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index cc1e76aa..5c73170c 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,7 @@ }, install_requires=[ - 'pyarmor.cli.core~=6.5.3' + 'pyarmor.cli.core~=7.6.0' ], entry_points={ diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index f3c2c619..5f65204f 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -20,7 +20,7 @@ # @Create Date: Thu Jan 12 17:29:25 CST 2023 # -__VERSION__ = '6.5.3' +__VERSION__ = '7.6.0' PLATFORM_NAMES = ( 'windows.x86_64', 'windows.x86', 'cygwin.x86_64', diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 019373b0..3125445e 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -6,7 +6,7 @@ minor = 6 patch = 0 ;; Compatible core version -cli.core = 6.5.3 +cli.core = 7.6.0 ;; Deprecated since Pyarmor 8.2.5 ; cli.runtime = 3.2.5 From 232cb1fb408ceb3e7a11999daa3c4295927d029b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 17 Oct 2024 21:38:13 +0800 Subject: [PATCH 024/337] Add data privacy --- docs/licenses.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index ece317d2..44fe33e2 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -129,7 +129,11 @@ Privacy License No. and product name will be embedded into obfuscated scripts, all the other user's information, for example, regname, email are not -For Pyarmor Basic and Pro License, only Pyarmor License file, serial number of hard disk, Ethernet address, IPv4/IPv6 address, and hostname will be sent to remote server for verification. No any user script will be uploaded to remote server +For Pyarmor Basic and Pro License, only Pyarmor License file, serial number of hard disk, Ethernet address, IPv4/IPv6 address, and hostname will be sent to Pyarmor License Server for verification + +When using Basic or CI License in CI/CD pipeline, some information about docker like docker name, ethernet address, IPv4/IPv6 address, and license information will be sent to Pyarmor License Server for verification + +No any user script will be uploaded to Pyarmor License Server Technical Support ================= From 6ac57ae0575a8dc0d6dc4f58a4e32a01856cf64b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 17 Oct 2024 21:38:29 +0800 Subject: [PATCH 025/337] Refine CI license usage --- docs/how-to/register.rst | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index bfa05f02..0f80e936 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -379,19 +379,22 @@ Since Pyarmor 9.0, Pyarmor Pro License and Pyarmor Group License couldn't be use Pyarmor Basic and CI License could be used in CI/CD pipeline by this way -- First generate ci regfile like pyarmor-CIxxxx-202410240512.zip:: +- First request CI regfile :file:`pyarmor-ci-xxxx.zip` by :term:`registration file` on local online machine:: pyarmor reg -C pyarmor-regfile-xxxx.zip - "pyarmor reg -C" only could be used 3 times in 24 hours, so do not use it in the pipeline + It will got one CI regfile :file:`pyarmor-ci-xxxx.zip` from Pyarmor License Server -- Then copy this file to each pipeline and register Pyarmor:: +- Then use :file:`pyarmor-ci-xxxx.zip` in the CI/CD pipeline to register Pyarmor:: - pyarmor reg pyarmor-CIxxxx-202410240512.zip + pyarmor reg pyarmor-ci-xxxx.zip -Make sure pipeline is online because it need do some online verification. +Important notes about CI regfile: -This ci regfile will be expired in 3 days, please generate new one on demand. +- CI regfile :file:`pyarmor-ci-xxxx.zip` is expired in 365 days +- After it expired, it need request new one +- CI regfile may not work in future Pyarmor version, after it doesn't work, try to rquest new one +- One license can request <= 100 CI regfiles Using multiple Pyarmor Licenses in same device ============================================== From b846b4c9ca5cdc92a641f7024f789ce31535afb7 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 18 Oct 2024 09:07:31 +0800 Subject: [PATCH 026/337] Refine Pyarmor 9 initial registration process --- src/cli/register.py | 125 +++++++++++++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 41 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index f100fdcb..aaaa47bd 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -21,7 +21,7 @@ # import os -from base64 import b64decode, urlsafe_b64encode +from base64 import b64decode, b64encode, urlsafe_b64encode from json import loads as json_loads from string import Template @@ -35,7 +35,38 @@ def parse_token(data): from struct import unpack - if not data or data.find(b' ') == -1: + if data and data.find(b' ') > 0: + try: + buf = b64decode(data.split()[0]) + + token, value = unpack('II', buf[:8]) + rev, features = value & 0xff, value >> 8 + licno = buf[16:34].decode('utf-8') + + pstr = [] + i = 64 + for k in range(4): + n = buf[i] + i += 1 + pstr.append(buf[i:i+n].decode('utf-8') if n + else '') + i += n + + product = ('non-profits(TBD)' if pstr[2] in ('', 'TBD') + else pstr[2]) + return { + 'token': token, + 'rev': rev, + 'features': features, + 'licno': licno, + 'machine': pstr[0], + 'regname': pstr[1], + 'product': product, + 'note': pstr[3], + } + except Exception as e: + logger.warning('bad token: %s', str(e)) + return { 'token': 0, 'rev': 0, @@ -46,32 +77,6 @@ def parse_token(data): 'note': 'This is trial license' } - buf = b64decode(data.split()[0]) - - token, value = unpack('II', buf[:8]) - rev, features = value & 0xff, value >> 8 - licno = buf[16:34].decode('utf-8') - - pstr = [] - i = 64 - for k in range(4): - n = buf[i] - i += 1 - pstr.append(buf[i:i+n].decode('utf-8') if n else '') - i += n - - product = 'non-profits(TBD)' if pstr[2] in ('', 'TBD') else pstr[2] - return { - 'token': token, - 'rev': rev, - 'features': features, - 'licno': licno, - 'machine': pstr[0], - 'regname': pstr[1], - 'product': product, - 'note': pstr[3], - } - class Register(object): @@ -193,13 +198,46 @@ def _register_offline_license(self, fzip, namelist): logger.debug('extracting %s', name) self.ctx.save_group_token(fzip.read(name)) + def _init_token(self, reginfo): + from struct import pack + rev, licno, lictp, regname, product = ( + reginfo['rev'], reginfo['rcode'], reginfo['type'], + reginfo['name'], reginfo['product'] + ) + + token = 0 + old_license = self.license_info + if old_license['licno'] == licno: + token = old_license['token'] + + features = (1 if lictp == 'J' else 7 if lictp == 'Z' else + 15 if lictp == 'G' else 23 if lictp == 'C' else + 0) + notes = ( + '* Do not use this file in CI/CD pipeline directly\n' + '* Only use it to request CI regfile ' + '"pyarmor-ci-%s.zip"' % licno[:6].lstrip('0') + if lictp == 'C' else '' + ) + + data = pack(' Date: Fri, 18 Oct 2024 17:28:48 +0800 Subject: [PATCH 027/337] Doc CI License usage --- docs/how-to/ci.rst | 40 ++++++-- docs/how-to/register.rst | 191 ++++++++++++++++++++---------------- docs/part-2.rst | 2 +- docs/reference/concepts.rst | 4 + 4 files changed, 145 insertions(+), 92 deletions(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 8545e757..8193bf06 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -4,18 +4,40 @@ Using Pyarmor in CI Pipeline ============================== -.. deprecated:: 9.0 +**Trial Version** could be used in CI/CD pipeline by one step:: - Refer to :ref:`Using Pyarmor in CI Pipeline` + pip install pyarmor -Pyarmor also could be used in CI/CD pipeline by this way: +For :term:`Pyarmor Basic` and :term:`Pyarmor CI` License -- First obfuscate the scripts by a few runner and store them to another branch like `master-obf` -- Then all the other runners continue the rest pipeline based on this branch like before +- Refer to :ref:`initial registration`, first got :term:`registration file` like ``pyarmor-regfile-xxxx.zip`` +- In local device run the following command to request one CI regfile ``pyarmor-ci-xxxx.zip``:: -Because only first step runners need register Pyarmor, so it could solve run limitions prior to Pyarmor 9.0 + pyarmor reg -C pyarmor-regfile-xxxx.zip -Suppose test-project locates at `https://github.com/dashingsoft/test-project`, the directory tree as follows:: +- In CI/CD pipeline, add 2 steps to register Pyarmor by CI regfile ``pyarmor-ci-xxxx.zip``:: + + pip install pyarmor + pyarmor reg pyarmor-ci-{rn}.zip + +- Check registration information in CI/CD pipeline:: + + pyarmor -v + +Notes + +* Do not request CI regfile in CI/CD pipeline +* CI regfile ``pyarmor-ci-xxxx.zip`` will be expired about in 360 days +* CI regfile may not work in future Pyarmor version +* Once CI regfile doesn't work, require new one +* One license can request <= 100 CI regfiles + +:term:`Pyarmor Pro` and :term:`Pyarmor Group` License can't be used in CI/CD pipeline directly, but there is one workaround + +- First obfuscate the scripts in local device and store them to another branch like `master-obf` +- Then in CI/CD pipeline to check this new branch + +Here is an example, suppose test-project locates at `https://github.com/dashingsoft/test-project`, the directory tree as follows:: $ tree test-project @@ -28,7 +50,7 @@ Suppose test-project locates at `https://github.com/dashingsoft/test-project`, t │ └── __init__.py └── __init__.py -The first runner will obfuscate the scripts and store them into another branch. Here it's an example bash script: +In local device the scripts are obfuscated and are stored into another branch: .. code-block:: bash @@ -56,6 +78,6 @@ The first runner will obfuscate the scripts and store them into another branch. # Push new branch to remote server $ git push -u origin master-obf -For all the other runners, they need not install Pyarmor, just checkout branch `master-obf`, and work as before. +In CI/CD pipeline, it need not install Pyarmor, just checkout branch `master-obf`, and work as before. .. include:: ../_common_definitions.txt diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 0f80e936..2ec6b4a8 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -16,36 +16,23 @@ Prerequisite First of all -1. An :term:`activation file` of :term:`Pyarmor License` like :file:`pyarmor-regcode-xxxx.txt`, refer to :doc:`../licenses` to purchase right one -2. Pyarmor 8.2+ +1. One :term:`activation file` of :term:`Pyarmor License`, refer to :doc:`../licenses` to purchase right one +2. One device has installed Pyarmor 9.0+ 3. Internet connection -4. Product name bind to this license, for non-commercial use, product name is ``non-profits`` +4. Product name which bind to this license -**If any firewall turns on** - -In Windows ``pytransform.pyd`` will connect to ``pyarmor.dashingsoft.com`` port ``80`` to request token for online obfuscation, in other platforms it is ``pytransform3.so``. Refer to firewall documentation to allow it to connect ``pyarmor.dashingsoft.com:80``. - -Using Pyarmor Basic or Pro -========================== - -Basic use steps: - -1. Using :term:`activation file` to initial registration, set product name bind to this license -2. Once initial registration completed, a :term:`registration file` is generated -3. Using :term:`registration file` to register Pyarmor in other devices +.. _initial registration: Initial registration --------------------- +==================== -Using :option:`-p` to specify product name for this license, for non-commercial use, set product name to ``non-profits``. - -Assume this license is used to protect your product ``XXX``, initial registration by this command:: +Any license need this step to request :term:`registration file` from Pyarmor License Server by :term:`activation file` like :file:`pyarmor-regcode-xxxx.txt`:: $ pyarmor reg -p "XXX" pyarmor-regcode-xxxx.txt -Pyarmor will show registration information and ask for your confirmation. If everything is fine, type :kbd:`yes` and :kbd:`Enter` to continue. Any other input aborts registration. +Using :option:`-p` to specify product name for this license, please replace "XXX" with real product name. For non-commercial use, replace it to ``non-profits``. -If initial registration is successful, it prints final license information in the console. And a :term:`registration file` like :file:`pyarmor-regfile-xxxx.zip` is generated in the current path at the same time. This file is used for subsequent registration in other machines. +If initial registration is successful, one :term:`registration file` like :file:`pyarmor-regfile-xxxx.zip` is generated in the current path at the same time. This file is used for subsequent registration in other machines. Once initial registration completed, activation file :file:`pyarmor-regcode-xxxx.txt` is invalid, do not use it again. @@ -66,10 +53,13 @@ In 6 months real product name must be set by this command:: If it's not changed after 6 months, the product name will be set to ``non-profits`` automatically and can't be changed again. -Registering in other machines ------------------------------ +Using Pyarmor Basic or Pro +========================== + +1. Refer to :ref:`initial registration`, got :term:`registration file` like `pyarmor-regfile-xxxx.zip` +2. Using :term:`registration file` to register Pyarmor in other devices -Copy :term:`registration file` :file:`pyarmor-regfile-xxxx.zip` to other machines, run the following command:: +Copy :term:`registration file` to other machines, then run this command:: $ pyarmor reg pyarmor-regfile-xxxx.zip @@ -79,6 +69,68 @@ Check the registration information:: After successful registration, all obfuscations will automatically apply this license, and each obfuscation requires online license verification. +This license can register Pyarmor on at most 100 devices + +Do not register Pyarmor in the CI/CD pipeline or docker container by this :term:`registration file`, each run will taken as one new device. + +.. seealso:: :doc:`ci` + +Using Pyarmor CI License +======================== + +.. versionadded:: 9.0 + +Refer to :ref:`initial registration`, got :term:`registration file` like `pyarmor-regfile-xxxx.zip` + +Do not use ``pyarmor-regfile-xxxx.zip`` in CI/CD pipeline directly, it's only used to request CI regfile: + +- In local device run the following command to request one CI regfile ``pyarmor-ci-xxxx.zip``:: + + pyarmor reg -C pyarmor-regfile-xxxx.zip + +- In CI/CD pipeline, add 2 steps to register Pyarmor by CI regfile ``pyarmor-ci-xxxx.zip``:: + + pip install pyarmor + pyarmor reg pyarmor-ci-{rn}.zip + +- Check registration information in CI/CD pipeline:: + + pyarmor -v + +Notes + +* Do not request CI regfile in CI/CD pipeline +* CI regfile ``pyarmor-ci-xxxx.zip`` will be expired about in 360 days +* CI regfile may not work in future Pyarmor version +* Once CI regfile doesn't work, require new one +* One license can request <= 100 CI regfiles + +.. _check device for group license: + +Check Device For Group License +============================== + +Check one device works for group license by this way: + +* First install Pyarmor 8.4.0+ trial version in this device +* Got machine id by the following command:: + + $ pyarmor reg -g 1 + ... + INFO current machine id is "mc92c9f22c732b482fb485aad31d789f1" + INFO device file has been generated successfully + +* Reboot this device, check machine id is same or not +* If machine id is same after each reboot, group license works in this device. Otherwise group license doesn't work in this device. + +For docker container, please check docker host as above. Only if docker host could work with group license, unlimited docker containers could be run in this docker host, refer to :doc:`how-to/register` section ``run unlimited dockers in offline device`` + +**If machine id of docker host is changed after reboot, group license doesn't work in any docker container** + +Most of physics machine, cloud server or VM like qemu, virtual box, vmware with same disk image work with Group license. Most of runners in CI/CD pipeline could not use Group License. + +.. _using group license: + Using group license =================== @@ -92,12 +144,11 @@ The allocated device No. is never free, if a device is reinstalled, it need allo Basic steps: -1. Using activation file :file:`pyarmor-regcode-xxxx.txt` to initial registration, set product name bind to this license, and generate :term:`registration file` [#]_ +1. Using activation file :file:`pyarmor-regcode-xxxx.txt` to initial registration, set product name bind to this license, and generate :term:`registration file` 2. Generating group device file separately on each offline device 3. Using :term:`registration file` and group device file to generate device registration file. 4. Using device registration file to register Pyarmor on offline device [#]_ -.. [#] Pyarmor will review group license manually and enable it in 24 hours since activation file is sent. .. [#] The device registration file is bind to specified device, each device has its own device regfile Initial registration @@ -365,37 +416,6 @@ Anyway, please configure the docker host/container network so that `pyarmor-auth If run `pyarmor-auth` in Linux VM or WSL, please check group license could work in Linux VM or WSL. It may need generate new device regfile for Linux VM or WSL. -.. _using pyarmor in ci pipeline: - -Using Pyarmor in CI Pipeline -============================= - -.. versionchanged:: 9.0 - Pyarmor Pro License couldn't be used in CI/CD pipeline since 9.0 - -For free version, just install pyarmor by `pip install pyarmor` in the pipeline. - -Since Pyarmor 9.0, Pyarmor Pro License and Pyarmor Group License couldn't be used in CI/CD pipeline. - -Pyarmor Basic and CI License could be used in CI/CD pipeline by this way - -- First request CI regfile :file:`pyarmor-ci-xxxx.zip` by :term:`registration file` on local online machine:: - - pyarmor reg -C pyarmor-regfile-xxxx.zip - - It will got one CI regfile :file:`pyarmor-ci-xxxx.zip` from Pyarmor License Server - -- Then use :file:`pyarmor-ci-xxxx.zip` in the CI/CD pipeline to register Pyarmor:: - - pyarmor reg pyarmor-ci-xxxx.zip - -Important notes about CI regfile: - -- CI regfile :file:`pyarmor-ci-xxxx.zip` is expired in 365 days -- After it expired, it need request new one -- CI regfile may not work in future Pyarmor version, after it doesn't work, try to rquest new one -- One license can request <= 100 CI regfiles - Using multiple Pyarmor Licenses in same device ============================================== @@ -419,50 +439,43 @@ Generally it need do nothing after upgrading Pyarmor, the registration informati But in the following versions something is changed -- **Pyarmor 8.0** Old license for Pyarmor 7 doesn't work +- **Pyarmor 8.0** + + Old license for Pyarmor 7 doesn't work - Some old licenses can be upgraded to Basic License freely, refer to :ref:`upgrade old license ` - Old license can't be upgraded to Pro or Group License -- **Pyarmor 9.0** For Group License it need generate device regfile again with Pyarmor 9.0+. The old device regfile which is generated in prior to Pyarmor 8.6 doesn't work in Pyarmor 9.0+ - - - First upgrade Pyarmor to 9.0+ in online device - - Then generate device regfile as first time. For example, generate device regfile ``pyarmor-device-regfile-6000.1.zip`` for device no. 1:: - - pyarmor reg -g 1 /path/to/pyarmor-regfile-6000.zip - - - Finally, replace old one with new one +- **Pyarmor 9.0** -.. _check device for group license: + A big change about using Pyarmor in CI/CD license -Check Device For Group License -============================== + - :term:`Pyarmor Basic` -Check one device works for group license by this way: + - :ref:`upgrade to pyarmor 9` freely + - If using Pyarmor in CI/CD pipeline, refer to :doc:`ci` -* First install Pyarmor 8.4.0+ trial version in this device -* Got machine id by the following command:: + - :term:`Pyarmor Pro` - $ pyarmor reg -g 1 - ... - INFO current machine id is "mc92c9f22c732b482fb485aad31d789f1" - INFO device file has been generated successfully + - If not using Pyarmor in CI/CD pipeline, :ref:`upgrade to pyarmor 9` freely + - If using Pyarmor in CI/CD pipeline, 2 choices -* Reboot this device, check machine id is same or not -* If machine id is same after each reboot, group license works in this device. Otherwise group license doesn't work in this device. + - Still use Pyarmor 8.x as before + - Upgrade to Pyarmor 9, it need upgrade :term:`Pyarmor Pro` to :term:`Pyarmor CI` -For docker container, please check docker host as above. Only if docker host could work with group license, unlimited docker containers could be run in this docker host, refer to :doc:`how-to/register` section ``run unlimited dockers in offline device`` + - :term:`Pyarmor CI` is paid per year + - Please contact pyarmor@163.com if need upgrade to :term:`Pyarmor CI` -**If machine id of docker host is changed after reboot, group license doesn't work in any docker container** + - :term:`Pyarmor Group` -Most of physics machine, cloud server or VM like qemu, virtual box, vmware with same disk image work with Group license. Most of runners in CI/CD pipeline could not use Group License. + It need generate device regfile for each offline device again by Pyarmor 9.0+, refer to :ref:`upgrade to pyarmor 9` .. _upgrading old license: Upgrading old license ===================== -Not all the old license could be upgraded to latest version. +Not all the old license (Pyarmor 7) could be upgraded to latest version. The old license could be upgraded to Pyarmor Basic freely only if it matches these conditions: @@ -500,4 +513,18 @@ After successful registration, all obfuscations will automatically apply this li If old license is used by many products (mainly old personal license), only one product could be used after upgrading. For the others, it need purchase new license. +.. _upgrade to pyarmor 9: + +Upgrade to Pyarmor 9 +-------------------- + + For Group License it need generate device regfile again with Pyarmor 9.0+. The old device regfile which is generated in prior to Pyarmor 8.6 doesn't work in Pyarmor 9.0+ + + - First upgrade Pyarmor to 9.0+ in online device + - Then generate device regfile as first time. For example, generate device regfile ``pyarmor-device-regfile-6000.1.zip`` for device no. 1:: + + pyarmor reg -g 1 /path/to/pyarmor-regfile-6000.zip + + - Finally, replace old one with new one + .. include:: ../_common_definitions.txt diff --git a/docs/part-2.rst b/docs/part-2.rst index 23db3c53..e4eee10d 100644 --- a/docs/part-2.rst +++ b/docs/part-2.rst @@ -10,8 +10,8 @@ how-to/packing how-to/wheel how-to/obfuscation + how-to/register how-to/ci how-to/third-party - how-to/register .. include:: _common_definitions.txt diff --git a/docs/reference/concepts.rst b/docs/reference/concepts.rst index 068a3555..2c7611a7 100644 --- a/docs/reference/concepts.rst +++ b/docs/reference/concepts.rst @@ -105,6 +105,10 @@ A :term:`Pyarmor License` type + Pyarmor CI + + A :term:`Pyarmor License` type + Pyarmor Group A :term:`Pyarmor License` type From 7e25917c5ab05e8fb26ab3f4a1d2f27763866494 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 18 Oct 2024 17:57:27 +0800 Subject: [PATCH 028/337] Fix conflict answers for new license --- docs/questions.rst | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/docs/questions.rst b/docs/questions.rst index 856d8c0c..38a50bdc 100644 --- a/docs/questions.rst +++ b/docs/questions.rst @@ -295,23 +295,9 @@ Hot Questions License ======= -**Which license is right for my project?** - - Hints for all licenses: - - - If using Python 2.7 or Python <= 3.6, then only Pyarmor old license works. All the new licenses only work for Python 3.7+ - - Each license only need pay once, but it may not work in Pyarmor future versions - - Hints for 3 new licenses (Pyarmor Basic, Pro and Gropu license): - - - If need offline obfuscation, only Pyarmor Group license works - - Pyarmor Basic and Pro license need internet connection to verify license when obfuscating the scripts, and in 24 hours only 100 different devices are allowed for each license - - In 24 hours, if need more than 100 docker containers to obfuscate your scripts, only Pyarmor Group license works. - - Pyarmor Basic, Pro and Group licenses don't work for Pyarmor 7. - **Will Pyarmor Pro license upload my scripts to remote server to verify license?** -No. For Pyarmor Basic and Pro License, only Pyarmor License file, serial number of hard disk, Ethernet address, IPv4/IPv6 address, and hostname will be sent to remote server for verification. + No. For Pyarmor Basic and Pro License, only Pyarmor License file, serial number of hard disk, Ethernet address, IPv4/IPv6 address, and hostname will be sent to remote server for verification. **I am interested to know if the users are entitled to updates to ensure compatibility with future versions of Python.** @@ -319,7 +305,7 @@ No. For Pyarmor Basic and Pro License, only Pyarmor License file, serial number **we use Docker to build/obfuscate the code locally then publish the Docker file to the client. After the build stage, the whole environment (and the license) is gone. I wonder how the workflow would be? Can I add the license file to the pipeline and register every time and build?** - It's no problem to run Pyarmor in Docker or CI pipeline to obfuscate application. Each build registering pyarmor with :file:`pyarmor-regfile-xxxx.zip` which is generated in initial registration. But It's not allowed to distribute package pyarmor and :term:`Pyarmor Basic`, :term:`Pyarmor Pro`, :term:`Pyarmor Group` License to customer, and don't run too many build dockers. + Please refer to :doc:`how-to/ci` **We are currently using a trial license for testing, but unfortunately our scripts are big and we are not able to statistically test the operation of Pyarmor. Do you have a commercial trial license for a certain trial period so that we can test the operation of Pyarmor for our scripts?** @@ -345,10 +331,7 @@ No. For Pyarmor Basic and Pro License, only Pyarmor License file, serial number **Which PyArmor 8.0 license for CI, more than 100 runs / day** - At this time, only Group License with self-host runner may work, the default runner which has volatile hardwares may not work. For CI with group license, there are 2 workarouds, please check section `using group license in CI pipeline`__ - -__ https://pyarmor.readthedocs.io/en/latest/how-to/register.html#using-group-license-in-ci-pipeline - + It's recommend to upgrade to Pyarmor 9, and use :term:`Pyarmor CI` License or :term:`Pyarmor Basic` License. See also :doc:`how-to/ci` .. **We intend to use PyArmor in CI to build obfuscated Docker images. According to the docs we can't use PyArmor in CI because the machine IDs will be different across each CI run (we verified this is the case), but according to this section in the docs, we can use PyArmor on "all the devices to develop, build, test the product", so we're a bit confused on whether this would work in CI. Could we buy a group license and then activate PyArmor as part of the CI pipeline? All of these different CI machines and developer laptops are building "one product"** @@ -365,7 +348,7 @@ Upgrading The old license is valid for ever. In this case need not upgrade old license to Pyarmor Basic license, just install Pyarmor 8.x, and using :command:`pyarmor-7` with old license. - Check :doc:`licenses` for more information about upgrading + Check :doc:`how-to/register` for more information about upgrading **If we upgrade the old license, will the current license expire? (no more available in terms of Pyarmor v7?** @@ -398,7 +381,6 @@ Upgrading __ https://pyarmor.readthedocs.io/en/stable/topic/repack.html __ https://github.com/dashingsoft/pyarmor/discussions/1107 -.. Purchasing ========== From 49b2937d486f34ab661f69e9948bba6e9b4122a8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 18 Oct 2024 18:13:50 +0800 Subject: [PATCH 029/337] Add notes --- docs/how-to/ci.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 8193bf06..0b61ec6e 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -32,6 +32,12 @@ Notes * Once CI regfile doesn't work, require new one * One license can request <= 100 CI regfiles +.. important:: + + In CI/CD pipeline, each run will send license and docker information to Pyarmor License Server, excessive requests or requests beyond normal usage may be rejected by Pyarmor License Server + + It's not allowed to install Pyarmor in your customer's docker image + :term:`Pyarmor Pro` and :term:`Pyarmor Group` License can't be used in CI/CD pipeline directly, but there is one workaround - First obfuscate the scripts in local device and store them to another branch like `master-obf` From cc82a06886fc0765ddcb2d6a7fcdc2f864b3f486 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 18 Oct 2024 22:00:26 +0800 Subject: [PATCH 030/337] Refine doc --- docs/how-to/register.rst | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 2ec6b4a8..895ad363 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -518,13 +518,40 @@ If old license is used by many products (mainly old personal license), only one Upgrade to Pyarmor 9 -------------------- - For Group License it need generate device regfile again with Pyarmor 9.0+. The old device regfile which is generated in prior to Pyarmor 8.6 doesn't work in Pyarmor 9.0+ +1. :term:`Pyarmor Basic` and :term:`Pyarmor Pro` - - First upgrade Pyarmor to 9.0+ in online device - - Then generate device regfile as first time. For example, generate device regfile ``pyarmor-device-regfile-6000.1.zip`` for device no. 1:: + If not used in CI/CD pipeline, - pyarmor reg -g 1 /path/to/pyarmor-regfile-6000.zip + - First upgrade to Pyarmor 9:: - - Finally, replace old one with new one + $ pip install -U pyarmor + + - Then use :term:`activation file` to generate new :term:`registration file`:: + + # Please replace XXX with real product name + $ pyarmor reg -p XXX pyarmor-regcode-xxxx.txt + + - Save and backup new :term:`registration file` ``pyarmor-regfile-xxxx.zip`` + + - Use this new regfile to register Pyarmor in any device:: + + $ pyarmor reg pyarmor-regfile-xxxx.zip + $ pyarmor -v + +2. :term:`Pyarmor Group` License + + It need generate device regfile again with Pyarmor 9.0+ + + - First upgrade to Pyarmor 9:: + + $ pip install -U pyarmor + + - Then generate device regfile as before + + For example, generate device regfile ``pyarmor-device-regfile-6000.1.zip`` for device no. 1:: + + $ pyarmor reg -g 1 /path/to/pyarmor-regfile-6000.zip + + - Finally, replace old with new one .. include:: ../_common_definitions.txt From 5d6d27c5a705081e2ce87d0ebd6efd527598dde8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 18 Oct 2024 22:23:44 +0800 Subject: [PATCH 031/337] Refine change logs --- README.md | 55 +++-------------------------------------------- docs/ChangeLogs.8 | 5 +++++ docs/ChangeLogs.9 | 9 ++++++++ 3 files changed, 17 insertions(+), 52 deletions(-) create mode 100755 docs/ChangeLogs.9 diff --git a/README.md b/README.md index 65783aba..a8be3609 100644 --- a/README.md +++ b/README.md @@ -94,64 +94,15 @@ Please read the [Pyarmor EULA](LICENSE). ## Changelog -Pyarmor 8.0 introduces significant changes. It has been rewritten and new features are implemented through the new commands: -`gen`, `reg`, `cfg`. These commands only work for Python 3.7 and above. - -Users of versions prior to 8.0 should read the [Import Notes][important-notes] section to decide whether to upgrade Pyarmor. - Each major version comes with a separate changelog file, detailing fixed issues, new features, and compatibility issues between different versions. Make sure to read the changelog carefully before upgrading Pyarmor: + - [Pyarmor 8.x Changelog](docs/ChangeLogs.8) +- [Pyarmor 9.x Changelog](docs/ChangeLogs.9) **Full changelogs** at [releases][releases] -**Upcoming features** at [Pyarmor 8.x Release Plan](ReleasePlan.md) +**Upcoming features** at [Pyarmor Release Plan](ReleasePlan.md) [releases]: https://github.com/dashingsoft/pyarmor/releases - -[important-notes]: #important-notes-for-users-of-pyarmor-prior-to-80 - -## Important Notes for Users of Pyarmor Prior to 8.0 - -Going forward, only bug fixes will be provided for older commands, such as `obfuscate` and `licenses`. -No new features will be added to these commands, but they will continue to be usable. - -Upon the release of Pyarmor 8.0, there are three scenarios for existing users: - -### 1. Never upgrade to version 8.0+ - -- **SPP mode will not work** - - To use SPP mode, you must upgrade Pyarmor to version 8.0 or later. - -- **`pyarmor register` command without arguments returns a `404` error** - - This command was used to query registration information in earlier versions of Pyarmor. However, the license server no longer supports this web API. Use `pyarmor -v` to ensure you are not using a trial version. - -- **Registering Pyarmor with `pyarmor register pyarmor-regcode-xxxxxx.txt` is limited to 10 uses**: - - To use Pyarmor on a new machine, CI server, or Docker, refer to the second method described in the registration file "pyarmor-regcode-xxxxxx.txt": -``` -Downloading "pyarmor-regfile-xxxxxx.zip" once, use this `.zip` file to register Pyarmor later. -``` - -### 2. Upgrade to version 8.0 but only use old features - -By default, the `pyarmor` command only accepts the new commands. - -To continue using older commands like `obfuscate` and `licenses`, you can: -- Use `pyarmor-7` instead of `pyarmor` -- Set the environment variable `PYARMOR_CLI=7` and continue using `pyarmor` -- Call the entry point `pyarmor.pyarmor:main_entry` in any other way - -### 3. Upgrade to version 8.0 and use new features - -- **New EULA**: - - Adhere to the new [Pyarmor EULA](LICENSE). This is a significant change for users with old personal licenses, as the new license only allows one product. - -- **License Upgrades**: - - Not all old licenses can be freely upgraded to the new license. Please refer to the [Pyarmor licenses][licenses] for more information. - -- **Internet Connection**: - - Older commands do not require an internet connection, but new commands do. - -- **Python Version Support**: - - While older commands support Python 2.7-3.10, new commands only support Python 3.7+. diff --git a/docs/ChangeLogs.8 b/docs/ChangeLogs.8 index 04eddd49..c35e8f6c 100755 --- a/docs/ChangeLogs.8 +++ b/docs/ChangeLogs.8 @@ -3,6 +3,9 @@ In order to improve security and support Python 3.11, there are significant changes in Pyarmor 8.0 +It has been rewritten and new features are implemented through the new commands: +`gen`, `reg`, `cfg`. These commands only work for Python 3.7 and above. + ## New features * Support Python 3.11 @@ -69,3 +72,5 @@ There are 3 cases for old users after Pyarmor 8.0 is released: Refer to https://github.com/dashingsoft/pyarmor#import-notes-for-pyarmor-prior-to-80 + +[licenses]: https://pyarmor.readthedocs.io/en/latest/licenses.html diff --git a/docs/ChangeLogs.9 b/docs/ChangeLogs.9 new file mode 100755 index 00000000..41a5eead --- /dev/null +++ b/docs/ChangeLogs.9 @@ -0,0 +1,9 @@ +# Pyarmor 9 Change Logs + +There is a big change about using Pyarmor in CI/CD license in Pyarmor 9 + +Pyarmor Pro License couldn't be used in CI/CD pipeline since Pyarmor 9 + +Use new license Pyarmor CI instead + +If not use Pyarmor License in CI/CD pipeline, almost nothing changed From 2f7835eb9c49f84242ee2f0d482eac3171c08848 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 18 Oct 2024 22:53:25 +0800 Subject: [PATCH 032/337] Check license before start --- docs/ChangeLogs.9 | 6 ++---- docs/how-to/register.rst | 2 +- src/cli/__main__.py | 5 ++++- src/cli/register.py | 30 +++++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/docs/ChangeLogs.9 b/docs/ChangeLogs.9 index 41a5eead..237a2535 100755 --- a/docs/ChangeLogs.9 +++ b/docs/ChangeLogs.9 @@ -1,9 +1,7 @@ # Pyarmor 9 Change Logs -There is a big change about using Pyarmor in CI/CD license in Pyarmor 9 +There is a big change about using Pyarmor in CI/CD pipeline -Pyarmor Pro License couldn't be used in CI/CD pipeline since Pyarmor 9 - -Use new license Pyarmor CI instead +Pyarmor Pro License couldn't be used in CI/CD pipeline since Pyarmor 9, use new license Pyarmor CI instead If not use Pyarmor License in CI/CD pipeline, almost nothing changed diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 895ad363..14df604c 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -448,7 +448,7 @@ But in the following versions something is changed - **Pyarmor 9.0** - A big change about using Pyarmor in CI/CD license + A big change about using Pyarmor in CI/CD pipeline - :term:`Pyarmor Basic` diff --git a/src/cli/__main__.py b/src/cli/__main__.py index b9f218ff..d28e22b2 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -26,7 +26,7 @@ from . import logger, CliError from .context import Context -from .register import Register, WebRegister +from .register import Register, WebRegister, check_license_version from .config import Configer from .shell import PyarmorShell from .plugin import Plugin @@ -222,6 +222,8 @@ def check_gen_context(ctx, args): def cmd_gen(ctx, args): options = format_gen_args(ctx, args) logger.debug('command options: %s', options) + check_license_version(ctx) + ctx.push(options) check_gen_context(ctx, args) @@ -710,6 +712,7 @@ def print_version(ctx): print('\n'.join(info)) reg.check_group_license() + check_license_version(ctx, silent=True) def get_home_paths(args): diff --git a/src/cli/register.py b/src/cli/register.py index aaaa47bd..539d8d94 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -78,6 +78,34 @@ def parse_token(data): } +def check_license_version(ctx, silent=False): + licinfo = ctx.license_info + rev = licinfo.get('rev', 0) + token = licinfo.get('token', 0) + features = licinfo.get('features', 0) + if rev == 1 and features > 0 and token > 0: + logger.warning('this license is not for Pyarmor 9') + if silent: + return False + + url = 'https://github.com/dashingsoft/pyarmor/issues/1958' + prompt = ( + '', + 'Pyarmor 9 has big change on CI/CD pipeline', + 'If not using Pyarmor License in CI/CD pipeline', + 'Press "c" to continue', + 'Otherwise press "h" to check Pyarmor 9.0 Upgrade Notes', + '', + 'Continue (c), Help (h), Quit (q): ' + ) + choice = input('\n'.join(prompt)).lower()[:1] + if choice == 'h': + import webbrowser + webbrowser.open(url) + if not choice == 'c': + raise SystemExit('Quit') + + class Register(object): def __init__(self, ctx): @@ -318,7 +346,7 @@ def generate_group_device(self, devid): return path def check_group_license(self, silent=False): - licinfo = self.ctx.license_info + licinfo = self.license_info if licinfo['features'] & 8: licmach = licinfo.get('machine', '') if not licmach: From 079ef29d4dd0603d7b941bba581e480ea4924691 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 07:13:19 +0800 Subject: [PATCH 033/337] Change version to 9.0.0 --- docs/conf.py | 4 ++-- src/cli/__init__.py | 2 +- src/cli/default.cfg | 4 ++-- src/config.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 94e31c33..1568d467 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,9 +57,9 @@ # built documents. # # The short X.Y version. -version = '8.6' +version = '9.0' # The full version, including alpha/beta/rc tags. -release = '8.6.0' +release = '9.0.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index e38247a7..d0bcc1e5 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '8.6.0' +__VERSION__ = '9.0.0' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 3125445e..582c9500 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -1,8 +1,8 @@ [pyarmor] ;; Pyarmor version -major = 8 -minor = 6 +major = 9 +minor = 0 patch = 0 ;; Compatible core version diff --git a/src/config.py b/src/config.py index 95572017..901786bc 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '8.6.0' +version = '9.0.0' # The corresponding version of pytransform.so core_version = 'r52.6' From ce5068acd0a9ab907fecb4efc132bbe52a5ae701 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 07:32:16 +0800 Subject: [PATCH 034/337] Remove debug hint --- src/cli/bug.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cli/bug.py b/src/cli/bug.py index 02e03715..50fb4376 100644 --- a/src/cli/bug.py +++ b/src/cli/bug.py @@ -118,7 +118,6 @@ def find_solutions(e): ''' logger = logging.getLogger() if logger.getEffectiveLevel() > logging.DEBUG: - logger.error(ENABLE_DEBUG_HINTS) return logger.info('generate bug file "%s"', BUG_FILE) From 6c7094c8135ab04da024e10a4a40a5d9fcbf4994 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 08:14:13 +0800 Subject: [PATCH 035/337] Refine doc --- README.md | 6 +++--- docs/how-to/register.rst | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a8be3609..f0a15336 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Please read the [Pyarmor EULA](LICENSE). ## Getting Help -1. **Consult the [Pyarmor 8.0 Documentation][doc].** +1. **Consult the [Pyarmor Documentation][doc].** 2. **Check the [FAQ][faq] for answers to common questions.** 3. **Try the documentation [index][genindex] or the [detailed table of contents][mastertoc].** 4. **If you still can't find the information you need, see [asking questions on GitHub][asking].** @@ -83,13 +83,13 @@ Please read the [Pyarmor EULA](LICENSE). ## Resources * [Website](https://pyarmor.dashingsoft.com) -* [Documentation 8.0][doc] +* [Documentation][doc] * [Documentation 7.x](https://pyarmor.readthedocs.io/en/v7.7/) 中文资源 * [Pyarmor 网站](https://pyarmor.dashingsoft.com/index-zh.html) -* [Pyarmor 8.0 在线文档](https://pyarmor.readthedocs.io/zh/latest/) +* [Pyarmor 在线文档](https://pyarmor.readthedocs.io/zh/latest/) * [Pyarmor 7.x 在线文档](https://pyarmor.readthedocs.io/zh/v7.x/) ## Changelog diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 14df604c..19bfbf8c 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -473,7 +473,7 @@ But in the following versions something is changed .. _upgrading old license: Upgrading old license -===================== +--------------------- Not all the old license (Pyarmor 7) could be upgraded to latest version. @@ -488,8 +488,7 @@ If failed to upgrade the old license, please purchase new license to use Pyarmor The old license can't be upgraded to Pyarmor Pro and Group. -Upgrading old license to Pyarmor Basic --------------------------------------- +**Upgrading old license to Pyarmor Basic** First find the activation file ``pyarmor-regcode-xxxx.txt``, which is sent to registration email when purchasing the license. From b8abad1a014336b45fc04ebcf74672fd6d1bd171 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 08:14:30 +0800 Subject: [PATCH 036/337] Show upgrade notes --- src/cli/bug.py | 5 +++-- src/cli/register.py | 22 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/cli/bug.py b/src/cli/bug.py index 50fb4376..5354306b 100644 --- a/src/cli/bug.py +++ b/src/cli/bug.py @@ -62,7 +62,7 @@ ENABLE_DEBUG_HINTS = 'please enable debug option `-d` to run it again' -SOLUTION_HINTS = Template('''something is wrong +SOLUTION_HINTS = Template(''' *=============================================================* * Please check console log to find out what's wrong * * * @@ -120,9 +120,10 @@ def find_solutions(e): if logger.getEffectiveLevel() > logging.DEBUG: return + print('\nDebug Information:') logger.info('generate bug file "%s"', BUG_FILE) generate_bug_report(e) - logger.error(SOLUTION_HINTS.substitute(bugfile=BUG_FILE)) + logger.info(SOLUTION_HINTS.substitute(bugfile=BUG_FILE)) if __name__ == '__main__': diff --git a/src/cli/register.py b/src/cli/register.py index 539d8d94..3c340e03 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -288,7 +288,27 @@ def register_regfile(self, regfile, clean=True): info = json_loads(f.read('reg.info'), encoding='utf-8') self.ctx.save_token(self._init_token(info)) else: - raise CliError('this license is not ready for Pyarmor 9') + self.show_upgrade_notes() + + def show_upgrade_notes(self): + if os.getenv('LANG', '').startswith('zh_CN'): + lang = 'zh', '#pyarmor' + else: + lang = 'en', '#what-need-to-do-after-upgrading-pyarmor' + docurl = ('https://pyarmor.readthedocs.io/' + '%s/v9.0/how-to/register.html%s' % lang) + prompt = ( + '', + 'Pyarmor 9 has big change on CI/CD pipeline', + 'Press "h" to check Pyarmor 9.0 Upgrade Notes', + '', + 'Help (h), Quit (q): ' + ) + choice = input('\n'.join(prompt)).lower()[:1] + if choice == 'h': + import webbrowser + webbrowser.open(docurl) + raise CliError('this license is not ready for Pyarmor 9') def _get_docker_hostname(self): try: From 3143ccfb1f31ddd51cff1987fcc8d9fa8b8e1395 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 08:19:57 +0800 Subject: [PATCH 037/337] Add doc 8 link --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f0a15336..0f36b021 100644 --- a/README.md +++ b/README.md @@ -84,12 +84,14 @@ Please read the [Pyarmor EULA](LICENSE). * [Website](https://pyarmor.dashingsoft.com) * [Documentation][doc] +* [Documentation 8.x](https://pyarmor.readthedocs.io/en/v8.5.12/) * [Documentation 7.x](https://pyarmor.readthedocs.io/en/v7.7/) 中文资源 * [Pyarmor 网站](https://pyarmor.dashingsoft.com/index-zh.html) * [Pyarmor 在线文档](https://pyarmor.readthedocs.io/zh/latest/) +* [Pyarmor 8.x 在线文档](https://pyarmor.readthedocs.io/zh/v8.5.12/) * [Pyarmor 7.x 在线文档](https://pyarmor.readthedocs.io/zh/v7.x/) ## Changelog From ecb2e0fd42992b88a7de1939060ab30368993da6 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 09:23:14 +0800 Subject: [PATCH 038/337] Refine prompt message --- src/cli/register.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index 3c340e03..f7a18326 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -625,9 +625,11 @@ def register(self, keyfile, product, upgrade=False, group=False): logger.info('this license has been activated sucessfully') notes = [ - '* Please backup regfile "%s" carefully, and ' + '* Please backup "%s", but do not use it to ' + 'register Pyarmor' % os.path.basename(keyfile), + '* Please backup regfile "%s", and ' 'use this file for next any registration' % regfile, - '* Do not use "%s" again' % os.path.basename(keyfile), + '* Do not use this file in docker and CI/CD pipeline', ] if group: @@ -637,8 +639,8 @@ def register(self, keyfile, product, upgrade=False, group=False): return notes.append('') - notes.append('Next register Pyarmor in any device by this command' - '(except docker and CI/CD pipeline):') + notes.append('Next register Pyarmor in build device by this command:') + notes.append('\tpyarmor reg %s' % regfile) notes.append('') logger.info('\n\nImport Notes:\n%s\n', '\n'.join(notes)) From d26824d00261f6609a09bded1f180736bf921e87 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 09:49:36 +0800 Subject: [PATCH 039/337] Refine code --- src/cli/register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index f7a18326..c6eebcbb 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -84,7 +84,7 @@ def check_license_version(ctx, silent=False): token = licinfo.get('token', 0) features = licinfo.get('features', 0) if rev == 1 and features > 0 and token > 0: - logger.warning('this license is not for Pyarmor 9') + logger.warning('this license is not ready for Pyarmor 9') if silent: return False From 0635f0909360dc882d67716a3fa7145901fd440a Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 11:09:41 +0800 Subject: [PATCH 040/337] Fix bugs --- src/cli/register.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index c6eebcbb..d627c5a5 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -248,17 +248,16 @@ def _init_token(self, reginfo): if lictp == 'C' else '' ) + regname = regname.encode('utf-8') + product = product.encode('utf-8') + notes = notes.encode('utf-8') data = pack(' Date: Sat, 19 Oct 2024 12:27:29 +0800 Subject: [PATCH 041/337] Note CI licnese only works in CI/CD pipeline --- docs/how-to/register.rst | 10 ++++++++-- docs/licenses.rst | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 19bfbf8c..ace2aa8d 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -75,8 +75,8 @@ Do not register Pyarmor in the CI/CD pipeline or docker container by this :term: .. seealso:: :doc:`ci` -Using Pyarmor CI License -======================== +Using Pyarmor CI +================ .. versionadded:: 9.0 @@ -105,6 +105,12 @@ Notes * Once CI regfile doesn't work, require new one * One license can request <= 100 CI regfiles +.. important:: + + :term:`Pyarmor CI` License doesn't work in local device + +.. seealso:: :doc:`ci` + .. _check device for group license: Check Device For Group License diff --git a/docs/licenses.rst b/docs/licenses.rst index 44fe33e2..07984e8b 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -58,7 +58,7 @@ There are some limitations in free version, for example, can't obfuscate big scr - 100 - 100 - 200 - - 20 + - N/A * - Unlimited local dockers [#]_ - - @@ -81,7 +81,7 @@ There are some limitations in free version, for example, can't obfuscate big scr - RFT Mode: renaming function/class/method/variable in Python scripts - BCC Mode: Transforming some Python functions in scripts to c functions, compile them to machine instructions directly .. [#] Offline build: the build device need not be online to verify Pyarmor License -.. [#] Maximum devices could install Pyarmor +.. [#] Maximum devices could install Pyarmor, CI license couldn't be used in local machine .. [#] Unlimited local dockers: run docker container in local machine, may be offline or in private network .. [#] Work in CI/CD pipeline: it need special option to register Pyarmor in CI/CD pipeline From 4fa6a3b5534a4daf94c4b37c8a27a559b79b06f3 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 12:38:11 +0800 Subject: [PATCH 042/337] Refine error message --- src/cli/register.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index d627c5a5..d0561c4d 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -280,11 +280,20 @@ def register_regfile(self, regfile, clean=True): self.ctx.save_token(f.read(name)) elif 'group.info' in namelist: docurl = 'http://pyarmor.readthedocs.io/en/stable' + logger.info('this file "%s" is only used to ' + 'request device regfile', regfile) logger.info('refer to %s/how-to/register.html' '#using-group-license', docurl) raise CliError('wrong usage for group license') elif 'reg.info' in namelist: - info = json_loads(f.read('reg.info'), encoding='utf-8') + data = f.read('reg.info') + info = json_loads(data, encoding='utf-8') + if info.get('features', 0) & 16: + logger.info('this file "%s" is only used to ' + 'request ci regfile', regfile) + logger.info('refer to %s/how-to/register.html' + '#using-ci-license', docurl) + raise CliError('wrong usage for ci license') self.ctx.save_token(self._init_token(info)) else: self.show_upgrade_notes() From e56602864e5c8b254c41c87037f9a5dfb5fb4063 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 12:38:25 +0800 Subject: [PATCH 043/337] Add link target --- docs/how-to/register.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index ace2aa8d..5a537503 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -75,7 +75,9 @@ Do not register Pyarmor in the CI/CD pipeline or docker container by this :term: .. seealso:: :doc:`ci` -Using Pyarmor CI +.. _using ci license: + +Using CI License ================ .. versionadded:: 9.0 From 4e94cd60d56d324332ca28208ceed9c0318faa07 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 12:53:33 +0800 Subject: [PATCH 044/337] Refine error message --- src/cli/register.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index d0561c4d..4e6c51d3 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -279,21 +279,18 @@ def register_regfile(self, regfile, clean=True): logger.debug('extracting %s', name) self.ctx.save_token(f.read(name)) elif 'group.info' in namelist: - docurl = 'http://pyarmor.readthedocs.io/en/stable' - logger.info('this file "%s" is only used to ' - 'request device regfile', regfile) - logger.info('refer to %s/how-to/register.html' - '#using-group-license', docurl) - raise CliError('wrong usage for group license') + logger.info('this file is only used to ' + 'request device regfile:\n\n' + '\tpyarmor reg -g 1 %s\n', regfile) + raise CliError('wrong usage for Group License') elif 'reg.info' in namelist: data = f.read('reg.info') info = json_loads(data, encoding='utf-8') - if info.get('features', 0) & 16: - logger.info('this file "%s" is only used to ' - 'request ci regfile', regfile) - logger.info('refer to %s/how-to/register.html' - '#using-ci-license', docurl) - raise CliError('wrong usage for ci license') + if info.get('type', '') == 'C': + logger.info('this file is only used to ' + 'request ci regfile:\n\n' + '\tpyarmor reg -c %s\n', regfile) + raise CliError('wrong usage for CI License') self.ctx.save_token(self._init_token(info)) else: self.show_upgrade_notes() From 31e5d653e803332eec889852017f9256ef6fff35 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 13:32:22 +0800 Subject: [PATCH 045/337] Refine doc --- docs/how-to/ci.rst | 15 ++++++++++----- docs/how-to/register.rst | 17 +++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 0b61ec6e..851b3bbd 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -13,16 +13,21 @@ For :term:`Pyarmor Basic` and :term:`Pyarmor CI` License - Refer to :ref:`initial registration`, first got :term:`registration file` like ``pyarmor-regfile-xxxx.zip`` - In local device run the following command to request one CI regfile ``pyarmor-ci-xxxx.zip``:: - pyarmor reg -C pyarmor-regfile-xxxx.zip + $ pyarmor reg -C pyarmor-regfile-xxxx.zip -- In CI/CD pipeline, add 2 steps to register Pyarmor by CI regfile ``pyarmor-ci-xxxx.zip``:: + Check CI license info in local machine:: - pip install pyarmor - pyarmor reg pyarmor-ci-{rn}.zip + $ pyarmor reg pyarmor-ci-xxxx.zip + +- In CI/CD pipeline, add 2 steps to register Pyarmor by CI regfile:: + + # Please replace "9.X.Y" with current Pyarmor version + pip install pyarmor=9.X.Y + pyarmor reg pyarmor-ci-xxxx.zip - Check registration information in CI/CD pipeline:: - pyarmor -v + pyarmor -v Notes diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 5a537503..2e54bffa 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -88,16 +88,21 @@ Do not use ``pyarmor-regfile-xxxx.zip`` in CI/CD pipeline directly, it's only us - In local device run the following command to request one CI regfile ``pyarmor-ci-xxxx.zip``:: - pyarmor reg -C pyarmor-regfile-xxxx.zip + $ pyarmor reg -C pyarmor-regfile-xxxx.zip -- In CI/CD pipeline, add 2 steps to register Pyarmor by CI regfile ``pyarmor-ci-xxxx.zip``:: + Check CI license info in local machine:: - pip install pyarmor - pyarmor reg pyarmor-ci-{rn}.zip + $ pyarmor reg pyarmor-ci-xxxx.zip -- Check registration information in CI/CD pipeline:: +- In CI/CD pipeline, add 2 steps to register Pyarmor by CI regfile:: - pyarmor -v + # Please replace "9.X.Y" with current Pyarmor version + pip install pyarmor=9.X.Y + pyarmor reg pyarmor-ci-xxxx.zip + + Check registration information in CI/CD pipeline:: + + pyarmor -v Notes From ccdf18f370742e6b9544ec7bd75127ab7f1e038d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 13:32:32 +0800 Subject: [PATCH 046/337] Refine code --- src/cli/__main__.py | 2 +- src/cli/register.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index d28e22b2..ec2a61e4 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -313,7 +313,7 @@ def cmd_reg(ctx, args): elif args.ci: regsvr = WebRegister(ctx) regsvr.check_request_interval() - regsvr.register_ci_license(regfile) + regsvr.request_ci_regfile(regfile) elif regfile.endswith('.zip'): reg = Register(ctx) diff --git a/src/cli/register.py b/src/cli/register.py index 4e6c51d3..02181bbd 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -289,7 +289,7 @@ def register_regfile(self, regfile, clean=True): if info.get('type', '') == 'C': logger.info('this file is only used to ' 'request ci regfile:\n\n' - '\tpyarmor reg -c %s\n', regfile) + '\tpyarmor reg -C %s\n', regfile) raise CliError('wrong usage for CI License') self.ctx.save_token(self._init_token(info)) else: @@ -776,7 +776,7 @@ def _write_ci_info(self, filename, data): with ZipFile(filename, 'a') as f: f.writestr('ci.token', data) - def register_ci_license(self, regfile, rev=2): + def request_ci_regfile(self, regfile, rev=2): logger.info('request ci regfile by "%s"', regfile) from zipfile import ZipFile From 28a00b80abb5562228f618a1261d20fe8c9dd661 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 13:56:01 +0800 Subject: [PATCH 047/337] Show hint for CI license --- src/cli/register.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index 02181bbd..973dfcf5 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -823,4 +823,12 @@ def request_ci_regfile(self, regfile, rev=2): dst.writestr(x, src.read(x)) dst.writestr('ci.token', token) - logger.info('generate ci regfile %s successfully', cifile) + logger.info('generate CI regfile "%s" successfully', cifile) + + ver = '.'.join([str(x) for x in self.ctx.version]) + logger.info('\n\nCheck CI license in local machine by:\n' + '\n\tpyarmor reg %s\n\n' + 'Register Pyarmor in CI/CD pipeline by:\n' + '\n\tpip install pyarmor==%s\n' + '\tpyarmor reg %s\n', + cifile, ver, cifile) From 709a26d62c1317c4bcbbc283b0d6693edbb49e98 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 18:03:53 +0800 Subject: [PATCH 048/337] Prompt for group license --- src/cli/register.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/cli/register.py b/src/cli/register.py index 973dfcf5..d8fe4dfe 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -89,6 +89,23 @@ def check_license_version(ctx, silent=False): return False url = 'https://github.com/dashingsoft/pyarmor/issues/1958' + # Group License + if features == 15: + prompt = ( + '', + 'Pyarmor 9 has some changes on license policy', + 'This group license is still available', + 'But it need request new device regfile as before', + 'Press "h" to check Pyarmor 9 Upgrade Notes', + '', + 'Help (h), Quit (q): ' + ) + choice = input('\n'.join(prompt)).lower()[:1] + if choice == 'h': + import webbrowser + webbrowser.open(url) + raise SystemExit('Quit') + prompt = ( '', 'Pyarmor 9 has big change on CI/CD pipeline', From 415e76cff9c2acfff2afdea2e448f6720f6b1e5e Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 18:55:26 +0800 Subject: [PATCH 049/337] Refine upgrade notes --- docs/ChangeLogs.9 | 79 ++++++++++++++++++++++++++++++++++++++-- docs/how-to/ci.rst | 2 +- docs/how-to/register.rst | 30 +++++++++++++-- docs/licenses.rst | 2 +- 4 files changed, 104 insertions(+), 9 deletions(-) diff --git a/docs/ChangeLogs.9 b/docs/ChangeLogs.9 index 237a2535..7f05b7b8 100755 --- a/docs/ChangeLogs.9 +++ b/docs/ChangeLogs.9 @@ -1,7 +1,80 @@ # Pyarmor 9 Change Logs -There is a big change about using Pyarmor in CI/CD pipeline +Because Pyarmor License is paid once in Pyarmor 8, but online verification for using Pyarmor License in CI/CD pipeline will continue consuming a lot of Pyarmor Server resources. -Pyarmor Pro License couldn't be used in CI/CD pipeline since Pyarmor 9, use new license Pyarmor CI instead +So Pyarmor 9 has a big change about using Pyarmor in CI/CD pipeline: -If not use Pyarmor License in CI/CD pipeline, almost nothing changed +- Pyarmor Pro License couldn't be used in CI/CD pipeline since Pyarmor 9 +- Pyarmor CI License is right for CI/CD pipeline case +- Pyarmor Basic still could be used in CI/CD pipeline, but it need request one extra CI regfile + +In a short + +**If not use Pyarmor License in CI/CD pipeline, almost nothing changed** +**If not upgrade to Pyarmor 9, but still use Pyarmor 8, nothing changed** +**Pyarmor Pro License purchased after Pyarmor 9 is released could not be used in CI/CD pipeline even use it with Pyarmor 8** + +Pyarmor CI License is paid each year, and couldn't be used in local machine. + +## Upgrade Basic or Pro License to Pyarmor 9 + +1. If Pyarmor License has been registerred in this device + + - First upgrade to Pyarmor 9 + ```bash + $ pip install -U pyarmor + ``` + + - When first time to obfuscate scripts, it will show hints + ```bash + $ pyarmor gen foo.py + + ... + Pyarmor 9 has big change on CI/CD pipeline + If not using Pyarmor License in CI/CD pipeline + Press "c" to continue + Otherwise press "h" to check Pyarmor 9.0 Upgrade Notes + + Continue (c), Help (h), Quit (q): + ``` + - Just press `c` to continue, there is no prompt later + +2. If Pyarmor License isn't registerred in this device + + - First use activation file to generate new registration file + ```bash + $ pip install -U pyarmor + + # Please replace XXX with real product name + $ pyarmor reg -p XXX pyarmor-regcode-xxxx.txt + ``` + - Save and backup new registration file `pyarmor-regfile-xxxx.zip` + + - Use this new regfile to register Pyarmor in other new device + ```bash + $ pyarmor reg pyarmor-regfile-xxxx.zip + $ pyarmor -v + ``` + + **If activation file is used too many times, please first install Pyarmor 8, then upgrade to Pyarmor 9** + +## Upgrade Group License to Pyarmor 9 + +It need generate device regfile again with Pyarmor 9.0+ + +- First upgrade to Pyarmor 9 + ```bash + $ pip install -U pyarmor + ``` + +- Then generate device regfile as before + + For example, generate device regfile `pyarmor-device-regfile-6000.1.zip` for device no. 1 + ```bash + $ pyarmor reg -g 1 /path/to/pyarmor-regfile-6000.zip + ``` + +- Finally, use new one to register Pyarmor in offline device + ```bash + $ pyarmor reg pyarmor-device-regfile-6000.1.zip + ``` diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 851b3bbd..56fd58ce 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -25,7 +25,7 @@ For :term:`Pyarmor Basic` and :term:`Pyarmor CI` License pip install pyarmor=9.X.Y pyarmor reg pyarmor-ci-xxxx.zip -- Check registration information in CI/CD pipeline:: + Check registration information in CI/CD pipeline:: pyarmor -v diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 2e54bffa..c6a34294 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -532,24 +532,44 @@ Upgrade to Pyarmor 9 1. :term:`Pyarmor Basic` and :term:`Pyarmor Pro` - If not used in CI/CD pipeline, + **If Pyarmor License has been registerred in this device** - First upgrade to Pyarmor 9:: $ pip install -U pyarmor - - Then use :term:`activation file` to generate new :term:`registration file`:: + - When first time to obfuscate scripts, it will show hints:: + + $ pyarmor gen foo.py + + ... + Pyarmor 9 has big change on CI/CD pipeline + If not using Pyarmor License in CI/CD pipeline + Press "c" to continue + Otherwise press "h" to check Pyarmor 9.0 Upgrade Notes + + Continue (c), Help (h), Quit (q): + + - Just press :kbd:`c` to continue, there is no prompt later + + **If Pyarmor License isn't registerred in this device** + + - First use :term:`activation file` to generate new :term:`registration file`:: + + $ pip install -U pyarmor # Please replace XXX with real product name $ pyarmor reg -p XXX pyarmor-regcode-xxxx.txt - Save and backup new :term:`registration file` ``pyarmor-regfile-xxxx.zip`` - - Use this new regfile to register Pyarmor in any device:: + - Use this new regfile to register Pyarmor in other new device:: $ pyarmor reg pyarmor-regfile-xxxx.zip $ pyarmor -v + If :term:`activation file` is used too many times, please first install Pyarmor 8, then upgrade to Pyarmor 9 + 2. :term:`Pyarmor Group` License It need generate device regfile again with Pyarmor 9.0+ @@ -564,6 +584,8 @@ Upgrade to Pyarmor 9 $ pyarmor reg -g 1 /path/to/pyarmor-regfile-6000.zip - - Finally, replace old with new one + - Finally, use new one to register Pyarmor in offline device:: + + $ pyarmor reg pyarmor-device-regfile-6000.1.zip .. include:: ../_common_definitions.txt diff --git a/docs/licenses.rst b/docs/licenses.rst index 07984e8b..34d5160a 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -58,7 +58,7 @@ There are some limitations in free version, for example, can't obfuscate big scr - 100 - 100 - 200 - - N/A + - 0 * - Unlimited local dockers [#]_ - - From 64318edf8a61d57c553b0db0ef9e49972fdf6299 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 20:13:56 +0800 Subject: [PATCH 050/337] Fix typos --- docs/ChangeLogs.9 | 9 ++++----- docs/how-to/register.rst | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/ChangeLogs.9 b/docs/ChangeLogs.9 index 7f05b7b8..d0a7d557 100755 --- a/docs/ChangeLogs.9 +++ b/docs/ChangeLogs.9 @@ -12,13 +12,12 @@ In a short **If not use Pyarmor License in CI/CD pipeline, almost nothing changed** **If not upgrade to Pyarmor 9, but still use Pyarmor 8, nothing changed** -**Pyarmor Pro License purchased after Pyarmor 9 is released could not be used in CI/CD pipeline even use it with Pyarmor 8** - -Pyarmor CI License is paid each year, and couldn't be used in local machine. +**Pyarmor Pro License purchased after 2024-10-20 could not be used in CI/CD pipeline even use it with Pyarmor 8** +**Pyarmor CI License is paid each year, and couldn't be used in local machine** ## Upgrade Basic or Pro License to Pyarmor 9 -1. If Pyarmor License has been registerred in this device +1. If Pyarmor License has been registered in this device - First upgrade to Pyarmor 9 ```bash @@ -39,7 +38,7 @@ Pyarmor CI License is paid each year, and couldn't be used in local machine. ``` - Just press `c` to continue, there is no prompt later -2. If Pyarmor License isn't registerred in this device +2. If Pyarmor License isn't registered in this device - First use activation file to generate new registration file ```bash diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index c6a34294..03ff668f 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -532,7 +532,7 @@ Upgrade to Pyarmor 9 1. :term:`Pyarmor Basic` and :term:`Pyarmor Pro` - **If Pyarmor License has been registerred in this device** + **If Pyarmor License has been registered in this device** - First upgrade to Pyarmor 9:: @@ -552,7 +552,7 @@ Upgrade to Pyarmor 9 - Just press :kbd:`c` to continue, there is no prompt later - **If Pyarmor License isn't registerred in this device** + **If Pyarmor License isn't registered in this device** - First use :term:`activation file` to generate new :term:`registration file`:: From c9d5cb2bf4d894dc23e6860e55fc8558dfbf6a60 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 20:14:06 +0800 Subject: [PATCH 051/337] Refine code --- src/cli/register.py | 54 +++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index d8fe4dfe..7073d4fb 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -31,6 +31,9 @@ # All supported machine flags for group license: [11, 26) MACHFLAGS = 22, 21, 18, 20, 16, 11 +# Upgrade notes for Pyarmor 9 +URL_UPGRADE_V9 = 'https://github.com/dashingsoft/pyarmor/issues/1958' + def parse_token(data): from struct import unpack @@ -78,6 +81,14 @@ def parse_token(data): } +def prompt_help_page(prompt, url): + choice = input('\n'.join(prompt)).lower()[:1] + if choice == 'h': + import webbrowser + webbrowser.open(url) + return choice + + def check_license_version(ctx, silent=False): licinfo = ctx.license_info rev = licinfo.get('rev', 0) @@ -88,7 +99,6 @@ def check_license_version(ctx, silent=False): if silent: return False - url = 'https://github.com/dashingsoft/pyarmor/issues/1958' # Group License if features == 15: prompt = ( @@ -100,10 +110,7 @@ def check_license_version(ctx, silent=False): '', 'Help (h), Quit (q): ' ) - choice = input('\n'.join(prompt)).lower()[:1] - if choice == 'h': - import webbrowser - webbrowser.open(url) + prompt_help_page(prompt, URL_UPGRADE_V9) raise SystemExit('Quit') prompt = ( @@ -115,11 +122,7 @@ def check_license_version(ctx, silent=False): '', 'Continue (c), Help (h), Quit (q): ' ) - choice = input('\n'.join(prompt)).lower()[:1] - if choice == 'h': - import webbrowser - webbrowser.open(url) - if not choice == 'c': + if not prompt_help_page(prompt, URL_UPGRADE_V9) == 'c': raise SystemExit('Quit') @@ -310,27 +313,16 @@ def register_regfile(self, regfile, clean=True): raise CliError('wrong usage for CI License') self.ctx.save_token(self._init_token(info)) else: - self.show_upgrade_notes() - - def show_upgrade_notes(self): - if os.getenv('LANG', '').startswith('zh_CN'): - lang = 'zh', '#pyarmor' - else: - lang = 'en', '#what-need-to-do-after-upgrading-pyarmor' - docurl = ('https://pyarmor.readthedocs.io/' - '%s/v9.0/how-to/register.html%s' % lang) - prompt = ( - '', - 'Pyarmor 9 has big change on CI/CD pipeline', - 'Press "h" to check Pyarmor 9.0 Upgrade Notes', - '', - 'Help (h), Quit (q): ' - ) - choice = input('\n'.join(prompt)).lower()[:1] - if choice == 'h': - import webbrowser - webbrowser.open(docurl) - raise CliError('this license is not ready for Pyarmor 9') + logger.error('this license is not ready for Pyarmor 9') + prompt = ( + '', + 'Pyarmor 9 has big change on CI/CD pipeline', + 'Press "h" to check Pyarmor 9.0 Upgrade Notes', + '', + 'Help (h), Quit (q): ' + ) + prompt_help_page(prompt, URL_UPGRADE_V9) + raise SystemExit('Quit') def _get_docker_hostname(self): try: From e70b5d07ff9eb84c5b5f9ed8c05bd559581aa78e Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 20:22:32 +0800 Subject: [PATCH 052/337] Refine code --- src/cli/register.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index 7073d4fb..97797f2a 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -34,6 +34,12 @@ # Upgrade notes for Pyarmor 9 URL_UPGRADE_V9 = 'https://github.com/dashingsoft/pyarmor/issues/1958' +# Template for license info +LICENSE_INFO_TEMPLATE = '''$advanced + +$notes +''' + def parse_token(data): from struct import unpack @@ -81,7 +87,7 @@ def parse_token(data): } -def prompt_help_page(prompt, url): +def show_help_page(prompt, url): choice = input('\n'.join(prompt)).lower()[:1] if choice == 'h': import webbrowser @@ -110,7 +116,7 @@ def check_license_version(ctx, silent=False): '', 'Help (h), Quit (q): ' ) - prompt_help_page(prompt, URL_UPGRADE_V9) + show_help_page(prompt, URL_UPGRADE_V9) raise SystemExit('Quit') prompt = ( @@ -122,7 +128,7 @@ def check_license_version(ctx, silent=False): '', 'Continue (c), Help (h), Quit (q): ' ) - if not prompt_help_page(prompt, URL_UPGRADE_V9) == 'c': + if not show_help_page(prompt, URL_UPGRADE_V9) == 'c': raise SystemExit('Quit') @@ -321,8 +327,8 @@ def register_regfile(self, regfile, clean=True): '', 'Help (h), Quit (q): ' ) - prompt_help_page(prompt, URL_UPGRADE_V9) - raise SystemExit('Quit') + show_help_page(prompt, URL_UPGRADE_V9) + raise SystemExit() def _get_docker_hostname(self): try: @@ -409,11 +415,6 @@ def check_group_license(self, silent=False): 'this group license is not for this machine') def __str__(self): - '''$advanced - -$notes -''' - info = self.license_info lictype = self._license_type(info) @@ -448,7 +449,7 @@ def __str__(self): if self.notes: self.notes.insert(0, 'Notes') - lines.append(Template(self.__str__.__doc__).substitute( + lines.append(Template(LICENSE_INFO_TEMPLATE).substitute( advanced='\n'.join(advanced), notes='\n'.join(self.notes), )) From ef09cffe639fb0fe9fa355b3eae55909947809a3 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 21:12:46 +0800 Subject: [PATCH 053/337] Modify License for Pyarmor 9 Limit build device numbers --- LICENSE | 25 +++++++++++++------------ LICENSE-ZH | 17 ++++++++--------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/LICENSE b/LICENSE index 44eac87e..e96ef11b 100755 --- a/LICENSE +++ b/LICENSE @@ -10,7 +10,7 @@ accessing or in any other way using Pyarmor - referred to as "user" or 1.1. "This Software" - means Pyarmor 8.0, it doesn't include Pyarmor prior to 8.0. Pyarmor 8.0 is + means Pyarmor 8.0+, it doesn't include Pyarmor prior to 8.0. Pyarmor 8.0 is rewritten, and provides new features. For compatibility, Pyarmor 8.0 also includes most of the old features, this license doesn't apply to those old features. It's only for new features. @@ -88,8 +88,8 @@ but there are some limitations. 2.1.2 Can not use feature Mix Str 2.1.3 Can not use feature RFT Mode, BCC Mode 2.1.4 Can not be used to obfuscate any commercial product. If the total sale - income of this product is less than 30x license fees, this software could - be used temporarily. + income of this product is less than 100 x license fees, this software + could be used temporarily. 2.1.5 Can not be used to provide obfuscation service in any form, in short this software can't be used to obfuscate the scripts of others @@ -106,6 +106,8 @@ Licensor issues 3 kind of Pyarmor License. Pyarmor Basic and Pyarmor Pro need internet connection to verify, it doesn't work in the device without internet connection. +Pyarmor Pro can not be used in CI/CD pipeline. + Pyarmor Group need not internet connection. Each Pyarmor License only need pay once, not periodically. @@ -116,14 +118,13 @@ Each Pyarmor License only need pay once, not periodically. Licensed Product has an unique License No. in global. If user has many products, each product need purchase one Pyarmor License. Except 2.6.1 -2.3.2 Pyarmor License could be installed in many machines and devices which - used to develop, build, debug, test and support Licensed Product. But - there is limitation to be used at the same time. See 2.3.3 +2.3.2 Pyarmor License could be installed in limited machines and devices which + used to develop, build, debug, test and support Licensed Product. 2.3.3 Pyarmor Basic and Pro License can only be used in no more than 100 - devices in 24 hours. Pyarmor License be used means use any feature of - Pyarmor in one machine. Running obfuscated scripts generated by - Pyarmor is not considered as Pyarmor License be used. + devices. Pyarmor License be used means use any feature of Pyarmor in + one machine. Running obfuscated scripts generated by Pyarmor is not + considered as Pyarmor License be used. 2.3.4 Pyarmor Basic and Pro License need internet connection to verify. @@ -162,9 +163,9 @@ copyright doctrines of fair use, fair dealing, or other equivalents. 2.6.1 If user has many products, and has purchased one license for the first product. The second product could use first product license only if sale - income of the second product less than 30x license fees. Once greater than - 30x license fees, the second product need purchase its own license. It's - same to user's other products. + income of the second product less than 100x license fees. Once greater + than 100x license fees, the second product need purchase its own license. + It's same to user's other products. 3. Responsibilities and Commitments ----------------------------------- diff --git a/LICENSE-ZH b/LICENSE-ZH index 18396d2d..e86b8122 100755 --- a/LICENSE-ZH +++ b/LICENSE-ZH @@ -105,8 +105,8 @@ BCC 加密模式 (2) 混淆字符串功能在试用版中无法使用。 (3) RFT 加密模式,BCC 加密模式在试用版无法使用。 (4) 不可以应用于加密商用产品。特别的,如果商用产品的累计销售额小于基础版许 - 可证费用乘以30,可以暂时使用;但是一旦累计销售额超过阀值,就不可以在继 - 续使用。 + 可证费用乘以 100,可以暂时使用;但是一旦累计销售额超过阀值,就不可以在 + 继续使用。 (5) 运行辅助包的名称 "pyarmor_runtime_000000" 不可以被设置和修改 (6) 不可以使用本软件提供任何形式的加密服务,不管是通过应用程序还是网络服务。 总之在任何情况下都不允许使用本软件加密其他人的脚本。 @@ -161,11 +161,11 @@ BCC 加密模式 如果用户有多个产品并且已经为第一个产品购买许可证,其他产品满足下列条件可以使用第 一个产品的许可证: -(1) 如果该产品的销售收入在当前许可证费用的10倍之内,那么该产品可以使用第一个产品 - 的许可证。 +(1) 如果该产品的销售收入在当前许可证费用的 100 倍之内,那么该产品可以使用第一个 + 产品的许可证。 -(2) 如果该产品的销售输入超过当前许可证费用的10倍,那么该产品不可以继续使用第一个 - 产品的许可证,需要单独购买新的许可证。 +(2) 如果该产品的销售输入超过当前许可证费用的 100 倍,那么该产品不可以继续使用 + 第一个产品的许可证,需要单独购买新的许可证。 本许可证并不旨在限制用户根据适用条款享有的任何权利、公平交易或其他等效的合理使用。 @@ -200,9 +200,8 @@ BCC 加密模式 4.2 用户可以修改本软件相关的 Python 脚本以满足使用方面的额外需求,但是修改后的脚 本只能在许可证范围内使用,不得分发给其它人 -4.3 同一个许可下面,同时使用本软件的设备数目不超过 100 个。 同时使用本软件是指从 - 现在开始在 24 小时内曾经运行 pyarmor 命令的设备。这里的设备是指安装本软件并 - 使用本软件对脚本进行加密的设备,不是指运行加密脚本的客户机器。 +4.3 同一个许可下面,使用本软件的设备数目有限制。这里的设备是指安装本软件并使用本 + 软件对脚本进行加密的设备,不是指运行加密脚本的客户机器。 4.4 每一个许可证绑定到一种产品,不可以转移给其它产品。 From e2ebf611652349f82c47d3ee2c71ae83b46338ff Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 21:33:08 +0800 Subject: [PATCH 054/337] Check supported version --- src/cli/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index ec2a61e4..29dc7861 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -731,6 +731,8 @@ def main_entry(argv): if sys.version_info[0] == 2 or sys.version_info[1] < 7: raise CliError('only Python 3.7+ is supported now') + elif sys.version_info[0] == 3 and sys.version_info[1] > 12: + raise CliError('Python 3.13+ is not supported now') ctx = Context(*get_home_paths(args)) From bdd95fb8659e58fb596bd9fedd0eff20330528de Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 21:38:38 +0800 Subject: [PATCH 055/337] Check more Python version --- src/cli/__main__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 29dc7861..09c70266 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -729,10 +729,14 @@ def main_entry(argv): parser = main_parser() args = parser.parse_args(argv) - if sys.version_info[0] == 2 or sys.version_info[1] < 7: + if sys.version_info[0] == 2: + raise CliError('Python 2 is not supported') + elif sys.version_info[0] == 3 and sys.version_info[1] < 7: raise CliError('only Python 3.7+ is supported now') elif sys.version_info[0] == 3 and sys.version_info[1] > 12: raise CliError('Python 3.13+ is not supported now') + else: + raise CliError('this Python version is not supported') ctx = Context(*get_home_paths(args)) From de32647c7adbb59e795f76820961c26bcbe5c2f6 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Oct 2024 23:27:34 +0800 Subject: [PATCH 056/337] Fix bugs --- src/cli/__main__.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 09c70266..726892a1 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -729,13 +729,8 @@ def main_entry(argv): parser = main_parser() args = parser.parse_args(argv) - if sys.version_info[0] == 2: - raise CliError('Python 2 is not supported') - elif sys.version_info[0] == 3 and sys.version_info[1] < 7: - raise CliError('only Python 3.7+ is supported now') - elif sys.version_info[0] == 3 and sys.version_info[1] > 12: - raise CliError('Python 3.13+ is not supported now') - else: + major, minor = sys.version_info[:2] + if not (major == 3 and minor > 6 and minor < 13): raise CliError('this Python version is not supported') ctx = Context(*get_home_paths(args)) From 09afef50f20d5f5a0b3130ec34a149d64b89678d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 20 Oct 2024 05:56:23 +0800 Subject: [PATCH 057/337] Refine code --- src/cli/__main__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 726892a1..5146f60a 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -729,9 +729,9 @@ def main_entry(argv): parser = main_parser() args = parser.parse_args(argv) - major, minor = sys.version_info[:2] - if not (major == 3 and minor > 6 and minor < 13): - raise CliError('this Python version is not supported') + x, y = sys.version_info[:2] + if not (x == 3 and y > 6 and y < 13): + raise CliError('Python %s.%s is not supported' % (x, y)) ctx = Context(*get_home_paths(args)) From da7f937ce38e7339ceaf44bc6e12ec945c73e332 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 20 Oct 2024 13:16:52 +0800 Subject: [PATCH 058/337] Note register Pyarmor once enough --- docs/how-to/register.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 03ff668f..d8fd426b 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -71,6 +71,8 @@ After successful registration, all obfuscations will automatically apply this li This license can register Pyarmor on at most 100 devices +On each device it's enough to register Pyarmor once, do not register Pyarmor before each obfuscation + Do not register Pyarmor in the CI/CD pipeline or docker container by this :term:`registration file`, each run will taken as one new device. .. seealso:: :doc:`ci` From fac0cbbc45c37fe0d7ff6ffd6399e4d655418fa5 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 20 Oct 2024 17:42:03 +0800 Subject: [PATCH 059/337] Change version to 9.0.1 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 1568d467..38ff3291 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.0' # The full version, including alpha/beta/rc tags. -release = '9.0.0' +release = '9.0.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index d0bcc1e5..cdcfef52 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.0.0' +__VERSION__ = '9.0.1' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 582c9500..d98e76ff 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 0 -patch = 0 +patch = 1 ;; Compatible core version cli.core = 7.6.0 diff --git a/src/config.py b/src/config.py index 901786bc..cfbef783 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.0.0' +version = '9.0.1' # The corresponding version of pytransform.so core_version = 'r52.6' From 4561e224c562346a697f9fe7d963ba54d97e49c8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 20 Oct 2024 17:54:21 +0800 Subject: [PATCH 060/337] Fix bug --- src/cli/register.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index 97797f2a..5ec9bae8 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -76,15 +76,15 @@ def parse_token(data): except Exception as e: logger.warning('bad token: %s', str(e)) - return { - 'token': 0, - 'rev': 0, - 'features': 0, - 'licno': 'pyarmor-vax-000000', - 'regname': '', - 'product': 'non-profits', - 'note': 'This is trial license' - } + return { + 'token': 0, + 'rev': 0, + 'features': 0, + 'licno': 'pyarmor-vax-000000', + 'regname': '', + 'product': 'non-profits', + 'note': 'This is trial license' + } def show_help_page(prompt, url): From ba8fe957f57c2ac9943ab952346f84426a1c3220 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 20 Oct 2024 21:14:50 +0800 Subject: [PATCH 061/337] Fix Python 3.9+ issue --- src/cli/register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index 5ec9bae8..40518ee7 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -311,7 +311,7 @@ def register_regfile(self, regfile, clean=True): raise CliError('wrong usage for Group License') elif 'reg.info' in namelist: data = f.read('reg.info') - info = json_loads(data, encoding='utf-8') + info = json_loads(data) if info.get('type', '') == 'C': logger.info('this file is only used to ' 'request ci regfile:\n\n' From 79fdf18e9b83b89bbe4708c23db03c158568ff81 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 20 Oct 2024 21:15:00 +0800 Subject: [PATCH 062/337] Change version to 9.0.2 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 38ff3291..43296b39 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.0' # The full version, including alpha/beta/rc tags. -release = '9.0.1' +release = '9.0.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index cdcfef52..6fda2fae 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.0.1' +__VERSION__ = '9.0.2' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index d98e76ff..72ee47c7 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 0 -patch = 1 +patch = 2 ;; Compatible core version cli.core = 7.6.0 diff --git a/src/config.py b/src/config.py index cfbef783..675512db 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.0.1' +version = '9.0.2' # The corresponding version of pytransform.so core_version = 'r52.6' From a9340c6fba1922cf034846766c46fa6b308bdff0 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 21 Oct 2024 12:10:19 +0800 Subject: [PATCH 063/337] Refine doc --- ReleasePlan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReleasePlan.md b/ReleasePlan.md index cd985e7d..92772908 100755 --- a/ReleasePlan.md +++ b/ReleasePlan.md @@ -1,6 +1,6 @@ -# Release Plan for Pyarmor 8 +# Release Plan for Pyarmor 8+ -The scheduled features for Pyarmor 8 +The scheduled features for Pyarmor 8+ **8.0** From 85163de5e824d3fcaa5d31d91743434eebe23bfa Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 21 Oct 2024 12:10:34 +0800 Subject: [PATCH 064/337] Change version to 9.0.3 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 43296b39..3f25e6e9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.0' # The full version, including alpha/beta/rc tags. -release = '9.0.2' +release = '9.0.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 6fda2fae..4565e17a 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.0.2' +__VERSION__ = '9.0.3' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 72ee47c7..ba0bde10 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 0 -patch = 2 +patch = 3 ;; Compatible core version cli.core = 7.6.0 diff --git a/src/config.py b/src/config.py index 675512db..d27266d3 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.0.2' +version = '9.0.3' # The corresponding version of pytransform.so core_version = 'r52.6' From 5c30f01a1fa624f2131a64279b76b16bfad4b020 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 21 Oct 2024 12:11:19 +0800 Subject: [PATCH 065/337] Fix group license register bug --- src/cli/register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index 40518ee7..5e3c2e73 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -717,7 +717,7 @@ def _write_group_info(self, filename, data): with ZipFile(filename, 'a') as f: f.writestr('group.info', data) - def register_group_device(self, regfile, devid, rev=1): + def register_group_device(self, regfile, devid, rev=2): from zipfile import ZipFile devfile = self.ctx.group_device_file(devid) logger.info('register device file "%s"', devfile) From d46bdc8d7b9091a917057b52009982eb81023257 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 21 Oct 2024 13:37:13 +0800 Subject: [PATCH 066/337] Refine code --- src/cli/register.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index 5e3c2e73..b1098504 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -687,8 +687,8 @@ def _handle_response(self, res): f.write(data[n:]) self._write_reg_info(filename, data[i:n]) else: - # TBD: it should not happened - logger.warning('no REGINFO found') + # Only for request group token + logger.debug('no REGINFO found') with open(filename, 'wb') as f: f.write(data) return filename From 45274c121118b46fc96bdbff4fe70b2649d3e7f0 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 21 Oct 2024 13:37:40 +0800 Subject: [PATCH 067/337] Change version to 9.0.4 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 3f25e6e9..5d6a162f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.0' # The full version, including alpha/beta/rc tags. -release = '9.0.3' +release = '9.0.4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 4565e17a..e0779ddf 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.0.3' +__VERSION__ = '9.0.4' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index ba0bde10..0dd91fac 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 0 -patch = 3 +patch = 4 ;; Compatible core version cli.core = 7.6.0 diff --git a/src/config.py b/src/config.py index d27266d3..2002155a 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.0.3' +version = '9.0.4' # The corresponding version of pytransform.so core_version = 'r52.6' From e3fb1d9689e9882ce7a2fa2139fed03de6812c79 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 21 Oct 2024 17:02:34 +0800 Subject: [PATCH 068/337] Move release plan to docs/ --- README.md | 2 +- docs/ReleasePlan.md | 84 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100755 docs/ReleasePlan.md diff --git a/README.md b/README.md index 0f36b021..6c990790 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,6 @@ Make sure to read the changelog carefully before upgrading Pyarmor: **Full changelogs** at [releases][releases] -**Upcoming features** at [Pyarmor Release Plan](ReleasePlan.md) +**Upcoming features** at [Pyarmor Release Plan](docs/ReleasePlan.md) [releases]: https://github.com/dashingsoft/pyarmor/releases diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md new file mode 100755 index 00000000..92772908 --- /dev/null +++ b/docs/ReleasePlan.md @@ -0,0 +1,84 @@ +# Release Plan for Pyarmor 8+ + +The scheduled features for Pyarmor 8+ + +**8.0** + +Status: released + +- Support Python 3.11 +- Introduce 3 new commands: gen, cfg, reg +- Introduce BCC Mode, an irreversible obfuscation method +- Introduce RFT mode, an irreversible obfuscation method +- Localize and internationalize runtime error messages +- Check expired date by NTP server + +**8.1** + +Status: released + +- Support platform darwin.aarch64 (Apple M1), linux.aarch64 +- Support platform windows.x86, linux.x86, linux.armv7 without BCC mode + +**8.2** + +Status: released (2023-05-10) + +- Full documentation +- Group license available +- Support plugins and hooks +- BCC mode for windows.x86, linux.x86, linux.armv7 +- Variable runtime package name for non trial version by `pyarmor cfg package_name_format "xxxx"` +- Clear frame locals by `pyarmor cfg clear_frame_locals 1` +- Improve pack security by + - protect system package + - bind interp + - check debugger + +**8.3** + +Status: relealsed (2023-08-01) + +- Support `--enable-themida` +- pyarmor-webui for 8.0 +- Support multiple Python versions +- Support platforms: Android, Alpine Linux (musl-c/docker), FreeBSD +- Support new arch `loongarch64` for Linux +- Group license supports unlimited dockers which uses default bridge network and not highly customized. + +**8.4** + +Status: released (2023-10-18) + +- Support Python 3.12 + +**8.5** + +Status: released (2024-03-08) + +- Expert users could write their own python code in pyarmor_runtime initialization +- New plugin "post_script" called after each script has been obfuscated +- Runtime key could be obfuscated +- No patch `pyarmor_runtime.so` for Apple Silcon so Windows users could build obfuscated scripts for Apple Silcon + +**9.0** + +Status: developing, released before Nov. 1 as plan + +- Pyarmor.man package with web-ui to help users learning, using Pyarmor, and quickly find solutions when something is wrong +- Add special Pyarmor CI License for CI/CD pipeline +- Full test + +**9.1** + +Status: not start + +- Support Python 3.13 +- Refine RFT mode to improve performance for big scripts +- FLY mode, a simple RFT mode, high performace goal, used by webapi like Djanjo +- Convert current unsupported node types as far as possible for BCC mode +- Expert users could customize C functions generated by BCC mode +- BCC mode support cross-platform (may not if too complex) +- Obfuscate string constants in extension module "pyarmor_runtime" (draft) + +Pyarmor status will be stable by the end of 2024 (Dec. 31, 2024) From 53a5af5581ed50eec703be1769aa26ca024da9c8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 21 Oct 2024 17:10:41 +0800 Subject: [PATCH 069/337] Refine folder --- .gitignore | 2 +- ReleasePlan.md | 84 -------------------- build-package.sh => scripts/build-package.sh | 2 + gh.py => scripts/gh.py | 0 4 files changed, 3 insertions(+), 85 deletions(-) delete mode 100755 ReleasePlan.md rename build-package.sh => scripts/build-package.sh (97%) rename gh.py => scripts/gh.py (100%) diff --git a/.gitignore b/.gitignore index 83ac52fd..53fa5280 100644 --- a/.gitignore +++ b/.gitignore @@ -102,4 +102,4 @@ ENV/ # pip .pypirc -pypi-upload.sh +scripts/pypi-upload.sh diff --git a/ReleasePlan.md b/ReleasePlan.md deleted file mode 100755 index 92772908..00000000 --- a/ReleasePlan.md +++ /dev/null @@ -1,84 +0,0 @@ -# Release Plan for Pyarmor 8+ - -The scheduled features for Pyarmor 8+ - -**8.0** - -Status: released - -- Support Python 3.11 -- Introduce 3 new commands: gen, cfg, reg -- Introduce BCC Mode, an irreversible obfuscation method -- Introduce RFT mode, an irreversible obfuscation method -- Localize and internationalize runtime error messages -- Check expired date by NTP server - -**8.1** - -Status: released - -- Support platform darwin.aarch64 (Apple M1), linux.aarch64 -- Support platform windows.x86, linux.x86, linux.armv7 without BCC mode - -**8.2** - -Status: released (2023-05-10) - -- Full documentation -- Group license available -- Support plugins and hooks -- BCC mode for windows.x86, linux.x86, linux.armv7 -- Variable runtime package name for non trial version by `pyarmor cfg package_name_format "xxxx"` -- Clear frame locals by `pyarmor cfg clear_frame_locals 1` -- Improve pack security by - - protect system package - - bind interp - - check debugger - -**8.3** - -Status: relealsed (2023-08-01) - -- Support `--enable-themida` -- pyarmor-webui for 8.0 -- Support multiple Python versions -- Support platforms: Android, Alpine Linux (musl-c/docker), FreeBSD -- Support new arch `loongarch64` for Linux -- Group license supports unlimited dockers which uses default bridge network and not highly customized. - -**8.4** - -Status: released (2023-10-18) - -- Support Python 3.12 - -**8.5** - -Status: released (2024-03-08) - -- Expert users could write their own python code in pyarmor_runtime initialization -- New plugin "post_script" called after each script has been obfuscated -- Runtime key could be obfuscated -- No patch `pyarmor_runtime.so` for Apple Silcon so Windows users could build obfuscated scripts for Apple Silcon - -**9.0** - -Status: developing, released before Nov. 1 as plan - -- Pyarmor.man package with web-ui to help users learning, using Pyarmor, and quickly find solutions when something is wrong -- Add special Pyarmor CI License for CI/CD pipeline -- Full test - -**9.1** - -Status: not start - -- Support Python 3.13 -- Refine RFT mode to improve performance for big scripts -- FLY mode, a simple RFT mode, high performace goal, used by webapi like Djanjo -- Convert current unsupported node types as far as possible for BCC mode -- Expert users could customize C functions generated by BCC mode -- BCC mode support cross-platform (may not if too complex) -- Obfuscate string constants in extension module "pyarmor_runtime" (draft) - -Pyarmor status will be stable by the end of 2024 (Dec. 31, 2024) diff --git a/build-package.sh b/scripts/build-package.sh similarity index 97% rename from build-package.sh rename to scripts/build-package.sh index 13d51db8..af0934d0 100755 --- a/build-package.sh +++ b/scripts/build-package.sh @@ -38,6 +38,8 @@ make_platform_files() # cp -a ${src}/macosx_x86_64 ${dst}/darwin/x86_64; cp -a plugins ./src/ } +# Change to workpath +cd ~/workspace/pyarmor # Make platform files make_platform_files diff --git a/gh.py b/scripts/gh.py similarity index 100% rename from gh.py rename to scripts/gh.py From d0f559474a87e1fe79ebfbfdcee2327577d1c42e Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 21 Oct 2024 17:18:36 +0800 Subject: [PATCH 070/337] Note this doc only for Pyarmor 7 --- plugins/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/README.md b/plugins/README.md index c0f157bf..52af40c7 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -1,5 +1,9 @@ # Plugins +***This document is written for Pyarmor 7*** + +***All examples in this folder not work in Pyarmor 8+*** + Plugin usually is used to extend license type, or insert some extra check code to obfuscated scripts to improve the security. From 836846495c103f6bf52bf1dbcc80277704ab5192 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 21 Oct 2024 18:39:00 +0800 Subject: [PATCH 071/337] Refine Release Plan --- docs/ReleasePlan.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md index 92772908..ae19aa55 100755 --- a/docs/ReleasePlan.md +++ b/docs/ReleasePlan.md @@ -75,10 +75,10 @@ Status: not start - Support Python 3.13 - Refine RFT mode to improve performance for big scripts -- FLY mode, a simple RFT mode, high performace goal, used by webapi like Djanjo +- FLY mode, a simple RFT mode, the goal is high performace after obfuscation - Convert current unsupported node types as far as possible for BCC mode -- Expert users could customize C functions generated by BCC mode -- BCC mode support cross-platform (may not if too complex) +- Expert users could customize C sources generated by BCC mode +- BCC mode support cross-platform (may not if too complex) (draft) - Obfuscate string constants in extension module "pyarmor_runtime" (draft) Pyarmor status will be stable by the end of 2024 (Dec. 31, 2024) From 475ae73ee302f85c2d8abbf49042dbe244917f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BCr=C5=9Fat=20Akta=C5=9F?= Date: Mon, 21 Oct 2024 14:42:57 +0300 Subject: [PATCH 072/337] Introducing Pyarmor Guru on Gurubase.io --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6c990790..5f0b966b 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Please read the [Pyarmor EULA](LICENSE). 4. **If you still can't find the information you need, see [asking questions on GitHub][asking].** 5. **[Report bugs][issues] following the issue template.** 6. **For business and security inquiries, send an email to .** +7. **You can also [Ask Pyarmor Guru][gurubase], it is a Pyarmor-focused AI to answer your questions.** [faq]: https://pyarmor.readthedocs.io/en/latest/questions.html [issues]: https://github.com/dashingsoft/pyarmor/issues @@ -79,6 +80,7 @@ Please read the [Pyarmor EULA](LICENSE). [mastertoc]: https://pyarmor.readthedocs.io/en/stable/index.html#table-of-contents [asking]: https://pyarmor.readthedocs.io/en/latest/questions.html#asking-questions-in-github [doc]: https://pyarmor.readthedocs.io/ +[gurubase]: https://gurubase.io/g/pyarmor ## Resources From b1a79450d2b5acba3394cc81d383f80560ff635f Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 21 Oct 2024 20:01:58 +0800 Subject: [PATCH 073/337] Remove unused folder --- man/dev.01 | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 man/dev.01 diff --git a/man/dev.01 b/man/dev.01 deleted file mode 100644 index e69de29b..00000000 From 8f806494feac820574df3fda614c322704439a0b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 22 Oct 2024 15:33:23 +0800 Subject: [PATCH 074/337] Add note for third-party learn platform --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f0b966b..e0b21ba9 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,10 @@ Please read the [Pyarmor EULA](LICENSE). 4. **If you still can't find the information you need, see [asking questions on GitHub][asking].** 5. **[Report bugs][issues] following the issue template.** 6. **For business and security inquiries, send an email to .** -7. **You can also [Ask Pyarmor Guru][gurubase], it is a Pyarmor-focused AI to answer your questions.** + +There are also some third-party learn platforms (not Pyarmor offical) + +- **[Ask Pyarmor Guru][gurubase], it is a Pyarmor-focused AI to answer your questions** [faq]: https://pyarmor.readthedocs.io/en/latest/questions.html [issues]: https://github.com/dashingsoft/pyarmor/issues From 59ce9cdf473233b14774e7957433eedda4bfe3c3 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 22 Oct 2024 15:52:01 +0800 Subject: [PATCH 075/337] Note for fair use Pyarmor Pro in CI/CD --- docs/licenses.rst | 4 +++- scripts/gh.py => gh.py | 0 2 files changed, 3 insertions(+), 1 deletion(-) rename scripts/gh.py => gh.py (100%) diff --git a/docs/licenses.rst b/docs/licenses.rst index 34d5160a..f4226160 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -81,7 +81,7 @@ There are some limitations in free version, for example, can't obfuscate big scr - RFT Mode: renaming function/class/method/variable in Python scripts - BCC Mode: Transforming some Python functions in scripts to c functions, compile them to machine instructions directly .. [#] Offline build: the build device need not be online to verify Pyarmor License -.. [#] Maximum devices could install Pyarmor, CI license couldn't be used in local machine +.. [#] Maximum devices could install Pyarmor, each docker run will be taken as one new build device, CI license only works in CI/CD pipeline .. [#] Unlimited local dockers: run docker container in local machine, may be offline or in private network .. [#] Work in CI/CD pipeline: it need special option to register Pyarmor in CI/CD pipeline @@ -120,6 +120,8 @@ Terms of Use If you have purchased Pyarmor License for one product, but you have aother product, and the total revenue of the other project is less than 100 x Pyarmor License fee, you could rent this license in your aother product + Pyarmor Pro License still could be used in CI/CD pipeline for low volume (100 runs per month) + In details check `Pyarmor EULA`_ .. seealso:: :doc:`Using Pyarmor Licenses ` diff --git a/scripts/gh.py b/gh.py similarity index 100% rename from scripts/gh.py rename to gh.py From a380e3038cc2311cf5e95c2eb32b5c09cd395d82 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 22 Oct 2024 16:30:50 +0800 Subject: [PATCH 076/337] Remove link --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index e0b21ba9..6c990790 100644 --- a/README.md +++ b/README.md @@ -73,17 +73,12 @@ Please read the [Pyarmor EULA](LICENSE). 5. **[Report bugs][issues] following the issue template.** 6. **For business and security inquiries, send an email to .** -There are also some third-party learn platforms (not Pyarmor offical) - -- **[Ask Pyarmor Guru][gurubase], it is a Pyarmor-focused AI to answer your questions** - [faq]: https://pyarmor.readthedocs.io/en/latest/questions.html [issues]: https://github.com/dashingsoft/pyarmor/issues [genindex]: https://pyarmor.readthedocs.io/en/stable/genindex.html [mastertoc]: https://pyarmor.readthedocs.io/en/stable/index.html#table-of-contents [asking]: https://pyarmor.readthedocs.io/en/latest/questions.html#asking-questions-in-github [doc]: https://pyarmor.readthedocs.io/ -[gurubase]: https://gurubase.io/g/pyarmor ## Resources From eb6bb9b20b9019238ac3b9a2c72e7cffaa1dddcf Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 22 Oct 2024 18:34:00 +0800 Subject: [PATCH 077/337] Add faq for CI licenses --- docs/questions.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/questions.rst b/docs/questions.rst index 38a50bdc..cfa98a42 100644 --- a/docs/questions.rst +++ b/docs/questions.rst @@ -332,6 +332,15 @@ License **Which PyArmor 8.0 license for CI, more than 100 runs / day** It's recommend to upgrade to Pyarmor 9, and use :term:`Pyarmor CI` License or :term:`Pyarmor Basic` License. See also :doc:`how-to/ci` + +**We should be able to assume that the CI regfile will keep working as long as it is within the license limits. Otherwise, builds might break at a moment notice. Could you confirm whether it is safe to assume that the CI license will keep working?** + + Fix Pyarmor version in the CI/CD pipeline, CI regfile works within the validity period. + +**About CI License, is there an option to allow offline usage in the CI/CD pipeline?** + + No. There is no offline option for CI License. + .. **We intend to use PyArmor in CI to build obfuscated Docker images. According to the docs we can't use PyArmor in CI because the machine IDs will be different across each CI run (we verified this is the case), but according to this section in the docs, we can use PyArmor on "all the devices to develop, build, test the product", so we're a bit confused on whether this would work in CI. Could we buy a group license and then activate PyArmor as part of the CI pipeline? All of these different CI machines and developer laptops are building "one product"** From d4e39b5f3ad0d90daa706d3da078bf994a9e93d8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 22 Oct 2024 18:34:41 +0800 Subject: [PATCH 078/337] Refine command to show CI License in local machine --- docs/how-to/ci.rst | 2 +- docs/how-to/register.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 56fd58ce..203a5f85 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -17,7 +17,7 @@ For :term:`Pyarmor Basic` and :term:`Pyarmor CI` License Check CI license info in local machine:: - $ pyarmor reg pyarmor-ci-xxxx.zip + $ pyarmor --home temp reg pyarmor-ci-xxxx.zip - In CI/CD pipeline, add 2 steps to register Pyarmor by CI regfile:: diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index d8fd426b..8f080f39 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -94,7 +94,7 @@ Do not use ``pyarmor-regfile-xxxx.zip`` in CI/CD pipeline directly, it's only us Check CI license info in local machine:: - $ pyarmor reg pyarmor-ci-xxxx.zip + $ pyarmor --home temp reg pyarmor-ci-xxxx.zip - In CI/CD pipeline, add 2 steps to register Pyarmor by CI regfile:: From 07c29e47d4d566906567107c4cb9514a5d6258f9 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 23 Oct 2024 20:37:03 +0800 Subject: [PATCH 079/337] Add link to Pyarmor Guru --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 6c990790..b849e122 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,10 @@ Please read the [Pyarmor EULA](LICENSE). 5. **[Report bugs][issues] following the issue template.** 6. **For business and security inquiries, send an email to .** +There are also some third-party learn platforms (not Pyarmor offical) + +- **[Ask Pyarmor Guru][gurubase], it is a Pyarmor-focused AI to answer your questions** + [faq]: https://pyarmor.readthedocs.io/en/latest/questions.html [issues]: https://github.com/dashingsoft/pyarmor/issues [genindex]: https://pyarmor.readthedocs.io/en/stable/genindex.html From 6fbf2f4d2fbc50be16c9c455307e4493eb990744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BCr=C5=9Fat=20Akta=C5=9F?= Date: Wed, 23 Oct 2024 17:42:42 +0300 Subject: [PATCH 080/337] fix gurubase link --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b849e122..e0b21ba9 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ There are also some third-party learn platforms (not Pyarmor offical) [mastertoc]: https://pyarmor.readthedocs.io/en/stable/index.html#table-of-contents [asking]: https://pyarmor.readthedocs.io/en/latest/questions.html#asking-questions-in-github [doc]: https://pyarmor.readthedocs.io/ +[gurubase]: https://gurubase.io/g/pyarmor ## Resources From 29439310b3a3ddc0be89d649a16144ce1a8ba02d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 27 Oct 2024 11:23:44 +0800 Subject: [PATCH 081/337] Refine pyarmor logging 1. Move logging config to default.cfg 2. Move log file to current path other than .pyarmor/ 3. No debug log in console, only in log file pyarmor.debug.log 4. Log command line to debug log when something is wrong --- src/cli/__main__.py | 69 ++++++++++++++++----------------------------- src/cli/bug.py | 9 +++++- src/cli/default.cfg | 63 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 90 insertions(+), 51 deletions(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 5146f60a..583b24a4 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -21,6 +21,7 @@ # import argparse import logging +import logging.config import os import sys @@ -32,7 +33,6 @@ from .plugin import Plugin from .generate import Builder from .bootstrap import check_prebuilt_runtime_library -from .bug import find_solutions def _cmd_gen_key(builder, options): @@ -667,43 +667,31 @@ def man_parser(subparsers): def log_settings(ctx, args): - if args.debug: - root = logging.getLogger() - root.setLevel(logging.DEBUG) - handler = logging.FileHandler(ctx.debug_logfile, - mode='w', - encoding='utf-8') - handler.setFormatter(logging.Formatter('%(asctime)s %(message)s')) - handler.setLevel(logging.DEBUG) - root.addHandler(handler) - - tracelog = logging.getLogger('trace') - tracelog.propagate = False - tracelog.addHandler(logging.NullHandler()) - if ctx.cfg.getboolean('builder', 'enable_trace'): - handler = logging.FileHandler(ctx.trace_logfile, - mode='w', - encoding='utf-8') - handler.setFormatter(logging.Formatter('%(name)-20s %(message)s')) - handler.setLevel(logging.DEBUG if args.debug else logging.INFO) - tracelog.addHandler(handler) + logging.config.fileConfig(ctx.default_config) if args.silent: - logging.getLogger().setLevel(100) + logger.setLevel(logging.ERROR) + + elif args.debug: + logger.setLevel(logging.DEBUG) + logger.handlers[1].setLevel(logging.DEBUG) + + if ctx.cfg.getboolean('builder', 'enable_trace'): + level = logging.DEBUG if args.debug else logging.INFO + logging.getLogger('trace').setLevel(level) + +def log_bug(e, unexpected=False): + logger.error(e) -def log_exception(e): - logger.debug('unknown error, please check pyarmor.error.log') - handler = logging.FileHandler('pyarmor.error.log', - mode='w', - encoding='utf-8') - fmt = '%(process)d %(processName)s %(asctime)s' - handler.setFormatter(logging.Formatter(fmt)) - log = logging.getLogger('error') - log.propagate = False - log.addHandler(logging.NullHandler()) - log.addHandler(handler) - log.exception(e) + logger.setLevel(logging.DEBUG) + logger.handlers[1].setLevel(logging.DEBUG) + + if unexpected: + from traceback import format_exc + logger.debug(format_exc()) + + logger.debug('command line\n%s', ' '.join(sys.argv)) def print_version(ctx): @@ -734,7 +722,6 @@ def main_entry(argv): raise CliError('Python %s.%s is not supported' % (x, y)) ctx = Context(*get_home_paths(args)) - log_settings(ctx, args) if args.version: @@ -758,21 +745,13 @@ def main_entry(argv): def main(): - logging.basicConfig( - level=logging.INFO, - format='%(levelname)-8s %(message)s', - ) - try: main_entry(sys.argv[1:]) except CliError as e: - logger.error(e) - find_solutions(e) + log_bug(e) sys.exit(1) except Exception as e: - log_exception(e) - logger.error(e) - find_solutions(e) + log_bug(e, unexpected=True) sys.exit(2) diff --git a/src/cli/bug.py b/src/cli/bug.py index 5354306b..69c5a122 100644 --- a/src/cli/bug.py +++ b/src/cli/bug.py @@ -18,7 +18,14 @@ # @Author: Jondy Zhao (pyarmor@163.com) # # @Create Date: Tue Mar 12 15:19:41 CST 2024 -# + + +############################################################# +# # +# Deprecated since Pyarmor 9, use `pyarmor man` instead # +# # +############################################################# + import logging from string import Template diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 0dd91fac..5aa8722d 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -19,11 +19,6 @@ timeout = 6 regurl = https://api.dashingsoft.com/product/key/enter/%s/? buyurl = https://jondy.github.io/paypal/index.html -[logging] - -debug_logfile = .pyarmor/pyarmor.debug.log -trace_logfile = .pyarmor/pyarmor.trace.log - [finder] recursive = 0 ;; includes = @@ -455,3 +450,61 @@ cflags = -O3 -Wno-unsequenced -fno-asynchronous-unwind-tables -fno-unwind-tables [freebsd.x86_64.bcc] cflags = -O3 -Wno-unsequenced -fno-asynchronous-unwind-tables -fno-unwind-tables -fPIC -fno-stack-protector -c + +;;;;;;;;;;;;;;;;;;;;;; +;; +;; Logging settings +;; +;;;;;;;;;;;;;;;;;;;;;; +[loggers] +keys=root,cli,trace + +[handlers] +keys=console,trace,debug + +[formatters] +keys=console,debug,trace + +[logger_root] +handlers=console + +[logger_cli] +level=INFO +handlers=console,debug +qualname=cli +propagate=0 + +[logger_trace] +level=ERROR +handlers=trace +qualname=trace +propagate=0 + +[handler_console] +class=StreamHandler +level=INFO +formatter=console +args=(sys.stdout,) + +[handler_debug] +class=FileHandler +level=ERROR +formatter=debug +args=('pyarmor.debug.log', 'w') +kwargs={'delay': True} + +[handler_trace] +class=FileHandler +level=DEBUG +formatter=trace +args=('pyarmor.trace.log', 'w') +kwargs={'delay': True} + +[formatter_console] +format=%(levelname)-8s %(message)s + +[formatter_debug] +format=%(asctime)s %(message)s + +[formatter_trace] +format=%(name)-20s %(levelname)s %(message)s From 8b3bdd25bcb3d6c32bc98af0711f172cbf677c60 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 27 Oct 2024 11:40:23 +0800 Subject: [PATCH 082/337] Change debug log usage --- docs/questions.rst | 16 ++++------------ docs/reference/man.rst | 9 +++++---- docs/tutorial/obfuscation.rst | 7 ++++--- src/cli/__main__.py | 2 +- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/docs/questions.rst b/docs/questions.rst index cfa98a42..02bc102e 100644 --- a/docs/questions.rst +++ b/docs/questions.rst @@ -83,12 +83,7 @@ If still no solution, please run pyarmor with debug option :option:`-d`. For exa $ pyarmor -d gen ... $ pyarmor -d reg ... -It will generate file :file:`pyarmor.report.bug` like this:: - - [Bug] `FileNotFoundError: [Errno 2] No such file or directory: 'aa.zip'` - - ### Full command options and console output - pyarmor -d reg aa.zip +It will generate file :file:`pyarmor.debug.bug` like this:: 2024-05-30 21:50:52,682 Python 3.7.10 2024-05-30 21:50:52,684 Pyarmor 8.5.9 (pro), 005068, btarmor @@ -98,14 +93,11 @@ It will generate file :file:`pyarmor.report.bug` like this:: 2024-05-30 21:50:52,696 register "aa.zip" 2024-05-30 21:50:52,698 unknown error, please check pyarmor.error.log 2024-05-30 21:50:52,704 [Errno 2] No such file or directory: 'aa.zip' + 2024-05-30 21:50:52,892 command line - ### Traceback - Traceback (most recent call last): - ... - self.fp = io.open(file, filemode) - FileNotFoundError: [Errno 2] No such file or directory: 'aa.zip' + pyarmor reg aa.zip -Take the first line `[Bug] ...` as bug title, and the rest content as bug body and make necessary supplements and explanations +Take it as part of bug body and make necessary supplements and explanations Pack issues ----------- diff --git a/docs/reference/man.rst b/docs/reference/man.rst index 5e3d7f8b..1df75a30 100644 --- a/docs/reference/man.rst +++ b/docs/reference/man.rst @@ -25,7 +25,7 @@ pyarmor -h, --help show available command set then quit -v, --version show version information then quit -q, --silent suppress all normal output :option:`... <-q>` --d, --debug show more information in the console :option:`... <-d>` +-d, --debug generate debug log file :option:`... <-d>` --home PATH set Pyarmor HOME path :option:`... <--home>` These options can be used after :program:`pyarmor` but before command, here are available commands: @@ -51,11 +51,12 @@ For example:: .. option:: -d, --debug - Show more information in the console + Generate debug log :file:`pyarmor.debug.log` -When something is wrong, print more debug information in the console. For example:: +When something is wrong, use this option to generate :file:`pyarmor.debug.log` to get more information. For example:: - pyarmor -d gen foo.py + $ pyarmor -d gen foo.py + $ cat pyarmor.debug.log .. option:: --home PATH[,GLOBAL[,LOCAL[,REG]]] diff --git a/docs/tutorial/obfuscation.rst b/docs/tutorial/obfuscation.rst index 52b98996..2966770c 100644 --- a/docs/tutorial/obfuscation.rst +++ b/docs/tutorial/obfuscation.rst @@ -36,18 +36,19 @@ Here only introduces common options in a short, using any combination of them as Debug mode and trace log ======================== -When something is wrong, check console log to find what Pyarmor does, and use :option:`-d` to enable debug mode to print more information:: +When something is wrong, check console log to find what Pyarmor does, and use :option:`-d` to generate :file:`pyarmor.debug.log` to get more information:: $ pyarmor -d gen foo.py + $ cat pyarmor.debug.log Trace log is useful to check whatever protected by Pyarmor, enable it by this command:: $ pyarmor cfg enable_trace=1 -After that, :ref:`pyarmor gen` will generate a logfile :file:`.pyarmor/pyarmor.trace.log`. For example:: +After that, :ref:`pyarmor gen` will generate a logfile :file:`pyarmor.trace.log`. For example:: $ pyarmor gen foo.py - $ cat .pyarmor/pyarmor.trace.log + $ cat pyarmor.trace.log trace.co foo:1: trace.co foo:5:hello diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 583b24a4..53784cbf 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -363,7 +363,7 @@ def main_parser(): ) parser.add_argument( '-d', '--debug', action='store_true', - help='print debug informations in the console' + help='generate debug file "pyarmor.debug.log"' ) parser.add_argument( '-i', dest='interactive', action='store_true', From 15880f55e38ef844d1b46f096e6af103b492692d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 27 Oct 2024 13:26:13 +0800 Subject: [PATCH 083/337] Fix testcases issues --- src/cli/default.cfg | 4 ++-- tests.8/accept_test.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 5aa8722d..3820aec8 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -484,7 +484,7 @@ propagate=0 class=StreamHandler level=INFO formatter=console -args=(sys.stdout,) +args=(sys.stderr,) [handler_debug] class=FileHandler @@ -507,4 +507,4 @@ format=%(levelname)-8s %(message)s format=%(asctime)s %(message)s [formatter_trace] -format=%(name)-20s %(levelname)s %(message)s +format=%(name)-20s %(message)s diff --git a/tests.8/accept_test.py b/tests.8/accept_test.py index a3a0191d..d4d6287b 100755 --- a/tests.8/accept_test.py +++ b/tests.8/accept_test.py @@ -313,7 +313,7 @@ def test_bcc_filter(self): ]) args = ['g', '--enable-bcc', 'samples/queens.py'] self.pyarmor_gen(args) - with open(os.path.join(self.local_path, 'pyarmor.trace.log')) as f: + with open('pyarmor.trace.log') as f: output = f.read() for line in ( 'trace.bcc ! queens:30:Queens.solve (excluded)', @@ -329,7 +329,7 @@ def test_rft_bcc_filter(self): ]) args = ['g', '--enable-bcc', '--enable-rft', 'samples/queens.py'] self.pyarmor_gen(args) - with open(os.path.join(self.local_path, 'pyarmor.trace.log')) as f: + with open('pyarmor.trace.log') as f: output = f.read() for line in ( 'trace.bcc ! queens:30', From 6b6f8707f48277dfdbf0286e618fd4b9f157b6de Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 27 Oct 2024 14:27:14 +0800 Subject: [PATCH 084/337] Log bug template to pyarmor.bug.log --- docs/questions.rst | 26 ++++++++-------------- src/cli/__main__.py | 54 ++++++++++++++++++++++++++++----------------- src/cli/default.cfg | 25 +++++++++++++++++---- 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/docs/questions.rst b/docs/questions.rst index 02bc102e..e83d7fa2 100644 --- a/docs/questions.rst +++ b/docs/questions.rst @@ -78,26 +78,18 @@ Build issues If there is error message when run pyarmor, please first check :doc:`reference/errors` to find solutions -If still no solution, please run pyarmor with debug option :option:`-d`. For example:: +If still no solution, please report issue based on :file:`pyarmor.bug.log` generated by Pyarmor automically. For example:: - $ pyarmor -d gen ... - $ pyarmor -d reg ... + [BUG]: no found input "fooxxx.py" -It will generate file :file:`pyarmor.debug.bug` like this:: + ## Command Line + pyarmor gen fooxxx.py - 2024-05-30 21:50:52,682 Python 3.7.10 - 2024-05-30 21:50:52,684 Pyarmor 8.5.9 (pro), 005068, btarmor - 2024-05-30 21:50:52,696 Platform darwin.x86_64 - 2024-05-30 21:50:52,696 native platform darwin.x86_64 - 2024-05-30 21:50:52,696 home path: /Users/jondy/.pyarmor - 2024-05-30 21:50:52,696 register "aa.zip" - 2024-05-30 21:50:52,698 unknown error, please check pyarmor.error.log - 2024-05-30 21:50:52,704 [Errno 2] No such file or directory: 'aa.zip' - 2024-05-30 21:50:52,892 command line - - pyarmor reg aa.zip - -Take it as part of bug body and make necessary supplements and explanations + ## Environments + Home /Users/jondy/.pyarmor + Platform darwin.x86_64 (darwin.x86_64) + Python 3.12.0 + Pyarmor 9.0.4 (group), 006000, btarmor Pack issues ----------- diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 53784cbf..14cea8f2 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -21,7 +21,6 @@ # import argparse import logging -import logging.config import os import sys @@ -344,7 +343,7 @@ def cmd_reg(ctx, args): def cmd_man(ctx, args): - logging.info('This feature is still developing ...') + logger.info('This feature is still developing ...') def main_parser(): @@ -647,19 +646,18 @@ def reg_parser(subparsers): def man_parser(subparsers): - '''Open Pyarmor.Man in web-browser + '''Start Pyarmor Man shell Pyarmor Man is designed to help Pyarmor users to learn and use Pyarmor, to find solution quickly when something is wrong, to - report bug and ask question by wizard in order to save both - Pyarmor team's and Pyarmor users' time. + report bug by template in order to save both Pyarmor team's + and users' time. ''' - cparser = subparsers.add_parser( 'man', formatter_class=argparse.RawDescriptionHelpFormatter, description=man_parser.__doc__, - help='register Pyarmor or upgrade old Pyarmor license' + help='start Pyarmor Man shell' ) cparser.set_defaults(func=cmd_man) @@ -667,6 +665,7 @@ def man_parser(subparsers): def log_settings(ctx, args): + import logging.config logging.config.fileConfig(ctx.default_config) if args.silent: @@ -681,17 +680,30 @@ def log_settings(ctx, args): logging.getLogger('trace').setLevel(level) -def log_bug(e, unexpected=False): - logger.error(e) - - logger.setLevel(logging.DEBUG) - logger.handlers[1].setLevel(logging.DEBUG) - - if unexpected: +def log_bug(e): + parser = main_parser() + args = parser.parse_args(sys.argv[1:]) + ctx = Context(*get_home_paths(args)) + cmdline = ' '.join(sys.argv) + buglog = logging.getLogger('cli.bug') + + buglog.info('[BUG]: %s\n', e) + buglog.info('## Command Line\n%s\n', cmdline) + buglog.info('## Environments', ) + buglog.info('Python %d.%d.%d', *sys.version_info[:3]) + buglog.info('Pyarmor %s', ctx.version_info()) + buglog.info('Platform %s', ctx.pyarmor_platform) + buglog.info('Native %s', ctx.native_platform) + buglog.info('Home %s', ctx.home_path) + buglog.info('') + + if not isinstance(e, CliError): from traceback import format_exc - logger.debug(format_exc()) + buglog.info('## Traceback\n%s\n', format_exc()) - logger.debug('command line\n%s', ' '.join(sys.argv)) + # reference/errors.html + # reference/solutions.html + # questions.html def print_version(ctx): @@ -717,13 +729,13 @@ def main_entry(argv): parser = main_parser() args = parser.parse_args(argv) + ctx = Context(*get_home_paths(args)) + log_settings(ctx, args) + x, y = sys.version_info[:2] if not (x == 3 and y > 6 and y < 13): raise CliError('Python %s.%s is not supported' % (x, y)) - ctx = Context(*get_home_paths(args)) - log_settings(ctx, args) - if args.version: print_version(ctx) parser.exit() @@ -748,10 +760,12 @@ def main(): try: main_entry(sys.argv[1:]) except CliError as e: + logger.error(e) log_bug(e) sys.exit(1) except Exception as e: - log_bug(e, unexpected=True) + logger.error(e) + log_bug(e) sys.exit(2) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 3820aec8..c4ff318d 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -18,6 +18,7 @@ timeout = 6 regurl = https://api.dashingsoft.com/product/key/enter/%s/? buyurl = https://jondy.github.io/paypal/index.html +docurl = https://pyarmor.readthedocs.io/{lang}/latest [finder] recursive = 0 @@ -457,13 +458,13 @@ cflags = -O3 -Wno-unsequenced -fno-asynchronous-unwind-tables -fno-unwind-tables ;; ;;;;;;;;;;;;;;;;;;;;;; [loggers] -keys=root,cli,trace +keys=root,cli,trace,bug [handlers] -keys=console,trace,debug +keys=console,trace,debug,bug [formatters] -keys=console,debug,trace +keys=console,debug,trace,bug [logger_root] handlers=console @@ -480,6 +481,12 @@ handlers=trace qualname=trace propagate=0 +[logger_bug] +level=INFO +handlers=bug +qualname=cli.bug +propagate=0 + [handler_console] class=StreamHandler level=INFO @@ -488,7 +495,7 @@ args=(sys.stderr,) [handler_debug] class=FileHandler -level=ERROR +level=CRITICAL formatter=debug args=('pyarmor.debug.log', 'w') kwargs={'delay': True} @@ -500,6 +507,13 @@ formatter=trace args=('pyarmor.trace.log', 'w') kwargs={'delay': True} +[handler_bug] +class=FileHandler +level=INFO +formatter=bug +args=('pyarmor.bug.log', 'w') +kwargs={'delay': True} + [formatter_console] format=%(levelname)-8s %(message)s @@ -508,3 +522,6 @@ format=%(asctime)s %(message)s [formatter_trace] format=%(name)-20s %(message)s + +[formatter_bug] +format=%(message)s From 7646fd8f4edff3e8256716c6b4f260735ba40af4 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 27 Oct 2024 15:03:52 +0800 Subject: [PATCH 085/337] Enable command: pyarmor man --- src/cli/__main__.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 14cea8f2..5d3ba6fb 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -343,7 +343,25 @@ def cmd_reg(ctx, args): def cmd_man(ctx, args): - logger.info('This feature is still developing ...') + from subprocess import check_call, check_output, STDOUT + + try: + m = __import__('pyarmor.man') + except ModuleNotFoundError: + m = None + + if m is None: + logger.info('pyarmor.man is still not installed') + if input('Install it ? (Y/n) ') in ('Y', 'y'): + cmdlist = [sys.executable, '-m', 'pip', + 'install', '-U', 'pyarmor.man'] + logger.info('installing package "pyarmor.man"...') + check_output(cmdlist, stderr=STDOUT) + logger.info('install package "pyarmor.man" OK') + else: + return + + check_call([sys.executable, '-m', 'pyarmor.man.shell']) def main_parser(): From a411cc324b5aac7078e29df5f28596eba839e1bf Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 27 Oct 2024 15:03:59 +0800 Subject: [PATCH 086/337] Refine code --- src/cli/shell.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cli/shell.py b/src/cli/shell.py index 62b1954c..5fb99ec5 100644 --- a/src/cli/shell.py +++ b/src/cli/shell.py @@ -21,7 +21,6 @@ # import configparser import cmd -import os class PyarmorShell(cmd.Cmd): From 31575b26c676071565f259d088871e3cdc1aa3f5 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 27 Oct 2024 17:01:07 +0800 Subject: [PATCH 087/337] Refine code --- src/cli/__main__.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 5d3ba6fb..50d75d57 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -348,17 +348,22 @@ def cmd_man(ctx, args): try: m = __import__('pyarmor.man') except ModuleNotFoundError: + logger.info('pyarmor.man is still not installed') + if input('Install it now? (Y/n) ') not in ('Y', 'y'): + return m = None if m is None: - logger.info('pyarmor.man is still not installed') - if input('Install it ? (Y/n) ') in ('Y', 'y'): - cmdlist = [sys.executable, '-m', 'pip', - 'install', '-U', 'pyarmor.man'] + try: logger.info('installing package "pyarmor.man"...') - check_output(cmdlist, stderr=STDOUT) + check_output([sys.executable, '-m', 'pip', + 'install', '-U', 'pyarmor.man'], + stderr=STDOUT) logger.info('install package "pyarmor.man" OK') - else: + except Exception: + logger.error('install package "pyarmor.man" failed') + logger.error('please install it manually:') + logger.error('\tpip install -U pyarmor.man') return check_call([sys.executable, '-m', 'pyarmor.man.shell']) From 05a24f656c4605ffa6ef22e16a11984f38720557 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 27 Oct 2024 17:04:29 +0800 Subject: [PATCH 088/337] Refine code --- src/cli/__main__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 50d75d57..33f39f6e 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -782,14 +782,10 @@ def main_entry(argv): def main(): try: main_entry(sys.argv[1:]) - except CliError as e: - logger.error(e) - log_bug(e) - sys.exit(1) except Exception as e: logger.error(e) log_bug(e) - sys.exit(2) + sys.exit(1 if isinstance(e, CliError) else 2) if __name__ == '__main__': From 9ebccd86625cc8a1ee37d93d1982aec7fd772e9d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 27 Oct 2024 23:15:11 +0800 Subject: [PATCH 089/337] Add man v0.2 --- man/dev.02 | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 man/dev.02 diff --git a/man/dev.02 b/man/dev.02 new file mode 100644 index 00000000..e69de29b From 611888a0ba663d829fa4386a0356a765eaea5759 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 30 Oct 2024 11:01:23 +0800 Subject: [PATCH 090/337] Fix issue #1970 --- src/cli/context.py | 2 +- src/cli/register.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/context.py b/src/cli/context.py index cf4f7fd9..5214fb42 100644 --- a/src/cli/context.py +++ b/src/cli/context.py @@ -301,7 +301,7 @@ def version_info(self, verbose=3): return rev licinfo = self.license_info - lictype = 'basic' if licinfo['features'] == 1 else \ + lictype = 'basic' if licinfo['features'] in (1, 17) else \ 'pro' if licinfo['features'] == 7 else \ 'group' if licinfo['features'] == 15 else \ 'ci' if licinfo['features'] == 23 else \ diff --git a/src/cli/register.py b/src/cli/register.py index b1098504..b1f21194 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -180,7 +180,7 @@ def license_info(self): return parse_token(self.ctx.read_token()) def _license_type(self, info): - return 'basic' if info['features'] == 1 else \ + return 'basic' if info['features'] in (1, 17) else \ 'pro' if info['features'] == 7 else \ 'group' if info['features'] == 15 else \ 'ci' if info['features'] == 23 else \ From 474324f53600022d833d672d9e50c521d0d9cb34 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 3 Nov 2024 10:01:57 +0800 Subject: [PATCH 091/337] log full command line for debug --- src/cli/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 33f39f6e..77d9a7e5 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -772,6 +772,7 @@ def main_entry(argv): logger.debug('native platform %s', ctx.native_platform) logger.debug('home path: %s', ctx.home_path) + logger.debug('args: %s', argv) if hasattr(args, 'func'): return args.func(ctx, args) From 4e8f5f33999b702b57555954db26deedd37f50cf Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 5 Nov 2024 14:09:22 +0800 Subject: [PATCH 092/337] change core version to 7.6.1 --- setup.py | 2 +- src/cli/core/__init__.py | 2 +- src/cli/default.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 5c73170c..0acaa084 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,7 @@ }, install_requires=[ - 'pyarmor.cli.core~=7.6.0' + 'pyarmor.cli.core~=7.6.1' ], entry_points={ diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index 5f65204f..ad7e9074 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -20,7 +20,7 @@ # @Create Date: Thu Jan 12 17:29:25 CST 2023 # -__VERSION__ = '7.6.0' +__VERSION__ = '7.6.1' PLATFORM_NAMES = ( 'windows.x86_64', 'windows.x86', 'cygwin.x86_64', diff --git a/src/cli/default.cfg b/src/cli/default.cfg index c4ff318d..2baeb85d 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -6,7 +6,7 @@ minor = 0 patch = 4 ;; Compatible core version -cli.core = 7.6.0 +cli.core = 7.6.1 ;; Deprecated since Pyarmor 8.2.5 ; cli.runtime = 3.2.5 From bd59ef0abfa9a5a8de8e2625cb23b8e482bef765 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 7 Nov 2024 12:31:42 +0800 Subject: [PATCH 093/337] Change version to 9.0.5 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 5d6a162f..79ec0e6b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.0' # The full version, including alpha/beta/rc tags. -release = '9.0.4' +release = '9.0.5' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index e0779ddf..aaf67a28 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.0.4' +__VERSION__ = '9.0.5' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 2baeb85d..ad60802b 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 0 -patch = 4 +patch = 5 ;; Compatible core version cli.core = 7.6.1 diff --git a/src/config.py b/src/config.py index 2002155a..9766897c 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.0.4' +version = '9.0.5' # The corresponding version of pytransform.so core_version = 'r52.6' From 70ee47bbd301fe734863b62eb3139e6e6370c0e4 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 7 Nov 2024 12:42:24 +0800 Subject: [PATCH 094/337] Add link for technical support Enable pyarmor man --- docs/licenses.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index f4226160..b38e12e3 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -146,10 +146,10 @@ Users need to learn Pyarmor features and how to use it by themself. Generally Py Pyarmor provides comprehensive learning systems, including but not limited to the following ways: -- Online documentation -- Checklist and FAQs could fix 90% issues reported to Pyarmor Team -- Discussions in Pyarmor project home -- Full examples to show each option usage and common cases by command ``pyarmor man`` (coming soon) +- :doc:`Online documentation ` +- :doc:`Checklist ` and :doc:`FAQs ` could fix 90% issues reported to Pyarmor Team +- `Discussions`_ in Pyarmor project home +- Full examples to show each option usage and common cases by command ``pyarmor man`` - Learn Pyarmor concepts by figure and animations in EKE Learning Platform (coming soon) Rome was not bulit in a day. Pyarmor Team keeps improving documentation and learning systems according to users feedback to make it easy and effects From 99d8269508c224699e55a8424d93ab4c63f77c10 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 7 Nov 2024 15:19:29 +0800 Subject: [PATCH 095/337] Doc report bug by `pyarmor man` --- docs/questions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/questions.rst b/docs/questions.rst index e83d7fa2..7e639755 100644 --- a/docs/questions.rst +++ b/docs/questions.rst @@ -67,7 +67,7 @@ A good report should have - Actual results - Expected results -For different issues, please following different guide to report bug, and do not paste snapshot image but paste text directly. +It's recommand to report issue by command `pyarmor man`. .. important:: From cc10a1128917c9e671a1f4fb2bc99a39016ecf93 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 8 Nov 2024 09:52:57 +0800 Subject: [PATCH 096/337] Move trace log to current path --- docs/topic/bccmode.rst | 6 +++--- docs/topic/rftmode.rst | 12 ++++++------ docs/tutorial/advanced.rst | 4 ++-- docs/tutorial/obfuscation.rst | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/topic/bccmode.rst b/docs/topic/bccmode.rst index 6196744d..09907549 100644 --- a/docs/topic/bccmode.rst +++ b/docs/topic/bccmode.rst @@ -33,8 +33,8 @@ To check which functions are converted to C function, enable trace mode before o Then check the trace log:: - $ ls .pyarmor/pyarmor.trace.log - $ grep trace.bcc .pyarmor/pyarmor.trace.log + $ ls pyarmor.trace.log + $ grep trace.bcc pyarmor.trace.log trace.bcc foo:5:hello trace.bcc foo:9:sum2 @@ -114,7 +114,7 @@ Let's enable trace mode to check these functions are ignored:: $ pyarmor cfg enable_trace 1 $ pyarmor gen --enable-bcc foo.py - $ grep trace.bcc .pyarmor/pyarmor.trace.log + $ grep trace.bcc pyarmor.trace.log Another example, in the following commands BCC mode ignores ``joker/card.py``, but handle all the other scripts in package ``joker``:: diff --git a/docs/topic/rftmode.rst b/docs/topic/rftmode.rst index 415d2a0d..2024208e 100644 --- a/docs/topic/rftmode.rst +++ b/docs/topic/rftmode.rst @@ -118,7 +118,7 @@ When both of trace log and trace rft are enabled, RFT mode will log which names $ pyarmor cfg enable_trace=1 trace_rft=1 $ pyarmor gen --enable-rft foo.py - $ grep trace.rft .pyarmor/pyarmor.trace.log + $ grep trace.rft pyarmor.trace.log trace.rft foo:1 (import sys as pyarmor__1) trace.rft foo:12 (self.wScan->self.pyarmor__4) @@ -137,7 +137,7 @@ When RFT scripts complain of name not found error, just exclude this name. For e If no found name like ``pyarmor__22``, find the original name in the trace log:: - $ grep pyarmor__22 .pyarmor/pyarmor.trace.log + $ grep pyarmor__22 pyarmor.trace.log trace.rft foo:65 (self.height->self.pyarmor__22) trace.rft foo:81 (self.height->self.pyarmor__22) @@ -218,7 +218,7 @@ First configure this ruler by command:: Then check the result:: $ pyarmor gen --enable-rft foo.py - $ grep trace.rft .pyarmor/pyarmor.trace.log + $ grep trace.rft pyarmor.trace.log trace.rft foo:8 (self.task.x->self.task.pyarmor__2) @@ -227,7 +227,7 @@ line 8 ``self.task.x`` will be transformed to ``self.task.pyarmor__2`` Let's change action to ``self.?.?``, and check the result:: $ pyarmor cfg rft_rulers "self.task.x self.?.?" - $ grep trace.rft .pyarmor/pyarmor.trace.log + $ grep trace.rft pyarmor.trace.log trace.rft foo:8 (self.task.x->self.pyarmor__1.pyarmor__2) @@ -236,7 +236,7 @@ Do not change action to ``?.?.?``, it doesn't work, the first action can't be `` Let's add new ruler to change ``self.task.y``, here need to use ``^`` to append new line to rulers:: $ pyarmor cfg rft_rulers ^"self.task.y self.?.?" - $ grep trace.rft .pyarmor/pyarmor.trace.log + $ grep trace.rft pyarmor.trace.log trace.rft foo:8 (self.task.x->self.pyarmor__1.pyarmor__2) trace.rft foo:9 (self.task.y->self.pyarmor__1.pyarmor__3) @@ -244,7 +244,7 @@ Let's add new ruler to change ``self.task.y``, here need to use ``^`` to append Actually, both of rulers can combined to one:: $ pyarmor cfg rft_rulers = "self.task.* self.?.?" - $ grep trace.rft .pyarmor/pyarmor.trace.log + $ grep trace.rft pyarmor.trace.log trace.rft foo:8 (self.task.x->self.pyarmor__1.pyarmor__2) trace.rft foo:9 (self.task.y->self.pyarmor__1.pyarmor__3) diff --git a/docs/tutorial/advanced.rst b/docs/tutorial/advanced.rst index 4cf7d6f7..a829e6c1 100644 --- a/docs/tutorial/advanced.rst +++ b/docs/tutorial/advanced.rst @@ -143,8 +143,8 @@ To check which functions are converted to C function, enable trace mode before o Then check the trace log:: - $ ls .pyarmor/pyarmor.trace.log - $ grep trace.bcc .pyarmor/pyarmor.trace.log + $ ls pyarmor.trace.log + $ grep trace.bcc pyarmor.trace.log trace.bcc foo:5:hello trace.bcc foo:9:sum2 diff --git a/docs/tutorial/obfuscation.rst b/docs/tutorial/obfuscation.rst index 2966770c..07990ca1 100644 --- a/docs/tutorial/obfuscation.rst +++ b/docs/tutorial/obfuscation.rst @@ -120,7 +120,7 @@ To check which function or which string are protected, enable trace log and chec $ pyarmor cfg enable_trace=1 $ pyarmor gen --mix-str --assert-call fib.py - $ cat .pyarmor/pyarmor.trace.log + $ cat pyarmor.trace.log trace.assert.call fib:10:'fib' trace.mix.str fib:1:'abcxyz' From 404e816356fff3bd58bfdc377faf9b286946f43c Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 8 Nov 2024 09:53:15 +0800 Subject: [PATCH 097/337] Enable Python 3.13 --- src/cli/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 77d9a7e5..84cfa465 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -756,7 +756,7 @@ def main_entry(argv): log_settings(ctx, args) x, y = sys.version_info[:2] - if not (x == 3 and y > 6 and y < 13): + if not (x == 3 and y > 6 and y < 14): raise CliError('Python %s.%s is not supported' % (x, y)) if args.version: From 8f541ba6b50532872932fa5169e966c99f61b71f Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 8 Nov 2024 22:21:45 +0800 Subject: [PATCH 098/337] Fix clear cfg issue --- src/cli/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/config.py b/src/cli/config.py index 1829ccb0..6c56f7d4 100644 --- a/src/cli/config.py +++ b/src/cli/config.py @@ -148,7 +148,7 @@ def _set_option(self, sect, opt, value, local=True, name=None): if not optvalue: if op is None: - self._clear(sect, optname, local, name) + self._remove(sect, [optname], local, name) return ctxcfg = ctx.cfg From ba2d0d232adaa13b383bcffb1b4b78a3ffd57b28 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 8 Nov 2024 22:30:16 +0800 Subject: [PATCH 099/337] Only enable DarwinUniversalPlugin for Darwin platform --- src/cli/plugin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cli/plugin.py b/src/cli/plugin.py index 7bb76ee1..628bb135 100644 --- a/src/cli/plugin.py +++ b/src/cli/plugin.py @@ -249,6 +249,9 @@ class DarwinUniversalPlugin: def post_build(ctx, inputs, outputs, pack): from shutil import rmtree + if not ctx.native_platform.startswith('darwin.'): + return + def rebuild_init(oneplat, init_script): with open(init_script, 'r') as f: lines = f.readlines() From 3bc310a7a463020706bf1123a5a009992e4931af Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 10 Nov 2024 09:03:27 +0800 Subject: [PATCH 100/337] Add python 3.13 to tests --- tests.8/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests.8/Makefile b/tests.8/Makefile index e0e3d017..f58519f7 100644 --- a/tests.8/Makefile +++ b/tests.8/Makefile @@ -14,7 +14,7 @@ ifeq ($(findstring Linux,$(UNAME)),Linux) PYTHONBIN := /usr/local/bin/pythonXY else HOST ?= linux.x86 - PYVERS := 3.7 3.8 3.9 3.10 3.11 3.12 + PYVERS := 3.7 3.8 3.9 3.10 3.11 3.12 3.13 PYTHONBIN := /usr/local/bin/pythonXY endif else ifeq ($(findstring Darwin,$(UNAME)),Darwin) @@ -24,7 +24,7 @@ else ifeq ($(findstring Darwin,$(UNAME)),Darwin) PYTHONBIN := /usr/local/bin/pythonXY else HOST ?= darwin.x86_64 - PYVERS := 3.7 3.8 3.9 3.10 3.11 3.12 + PYVERS := 3.7 3.8 3.9 3.10 3.11 3.12 3.13 PYTHONBIN := /usr/local/bin/pythonXY endif else From 271beb34d7ad01c4d1efe6f1748f30699e0ac979 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 10 Nov 2024 09:44:32 +0800 Subject: [PATCH 101/337] Add python 3.13 to tests for linux.x86_64 --- tests.8/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests.8/Makefile b/tests.8/Makefile index f58519f7..2d146a7f 100644 --- a/tests.8/Makefile +++ b/tests.8/Makefile @@ -6,7 +6,7 @@ ARCH := $(shell arch) ifeq ($(findstring Linux,$(UNAME)),Linux) ifeq ($(ARCH),x86_64) HOST ?= linux.x86_64 - PYVERS := 3.7 3.8 3.9 3.10 3.11 3.12 + PYVERS := 3.7 3.8 3.9 3.10 3.11 3.12 3.13 PYTHONBIN := /usr/local/bin/pythonXY else ifeq ($(ARCH),aarch64) HOST ?= linux.aarch64 From d0e11392671dd916207859b6e6710159b37f4a54 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 10 Nov 2024 09:46:21 +0800 Subject: [PATCH 102/337] Enable patch compitable --- src/cli/bootstrap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/bootstrap.py b/src/cli/bootstrap.py index af961433..10ef540a 100644 --- a/src/cli/bootstrap.py +++ b/src/cli/bootstrap.py @@ -47,7 +47,7 @@ def check_prebuilt_runtime_library(platnames, extra=None, rtver=''): if current_rtver == rtver: return - pkgver = 'pyarmor.cli.runtime==%s' % rtver + pkgver = 'pyarmor.cli.runtime~=%s' % rtver logging.info('install "%s" for cross platforms', pkgver) try: return check_call(instcmd + [pkgver]) @@ -73,7 +73,7 @@ def check_prebuilt_runtime_library(platnames, extra=None, rtver=''): pkgnames.remove(entry.name) if pkgnames: - pkgvers = ['pyarmor.cli.core.%s==%s' % (x, corever) for x in pkgnames] + pkgvers = ['pyarmor.cli.core.%s~=%s' % (x, corever) for x in pkgnames] logging.info('install packages %s for cross platforms', str(pkgvers)) try: check_call(instcmd + pkgvers) From 4b0967e0cdda1a6271b84b018859f57036ffddbf Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 10 Nov 2024 09:46:49 +0800 Subject: [PATCH 103/337] change core version to 7.6.2 --- setup.py | 2 +- src/cli/core/__init__.py | 2 +- src/cli/default.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 0acaa084..e6a011ab 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,7 @@ }, install_requires=[ - 'pyarmor.cli.core~=7.6.1' + 'pyarmor.cli.core~=7.6.2' ], entry_points={ diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index ad7e9074..3555ccdd 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -20,7 +20,7 @@ # @Create Date: Thu Jan 12 17:29:25 CST 2023 # -__VERSION__ = '7.6.1' +__VERSION__ = '7.6.2' PLATFORM_NAMES = ( 'windows.x86_64', 'windows.x86', 'cygwin.x86_64', diff --git a/src/cli/default.cfg b/src/cli/default.cfg index ad60802b..4750bf31 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -6,7 +6,7 @@ minor = 0 patch = 5 ;; Compatible core version -cli.core = 7.6.1 +cli.core = 7.6.2 ;; Deprecated since Pyarmor 8.2.5 ; cli.runtime = 3.2.5 From c20c3a7b54cc462bb91737e84eda1e9e8a3df594 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 10 Nov 2024 13:24:27 +0800 Subject: [PATCH 104/337] Change version to 9.0.6 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 79ec0e6b..636dc390 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.0' # The full version, including alpha/beta/rc tags. -release = '9.0.5' +release = '9.0.6' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index aaf67a28..044139fc 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.0.5' +__VERSION__ = '9.0.6' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 4750bf31..35c59c48 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 0 -patch = 5 +patch = 6 ;; Compatible core version cli.core = 7.6.2 diff --git a/src/config.py b/src/config.py index 9766897c..87e184a6 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.0.5' +version = '9.0.6' # The corresponding version of pytransform.so core_version = 'r52.6' From 534230e37193ce2fd9d6a126ee2a4846ca916577 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 13 Nov 2024 12:27:46 +0800 Subject: [PATCH 105/337] Doc support Python 3.13 --- docs/reference/environments.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/environments.rst b/docs/reference/environments.rst index 5653b207..fe59b307 100644 --- a/docs/reference/environments.rst +++ b/docs/reference/environments.rst @@ -21,11 +21,11 @@ Supported Python versions :widths: auto =================== ===== ========= ========== ====== ====== ======= ============== - Python Version 2.7 3.0~3.6 3.7~3.10 3.11 3.12 3.13+ Remark + Python Version 2.7 3.0~3.6 3.7~3.10 3.11 3.12 3.13 Remark =================== ===== ========= ========== ====== ====== ======= ============== - pyarmor 8 RFT Mode No No Y Y Y N/y [#]_ - pyarmor 8 BCC Mode No No Y Y Y N/y - pyarmor 8 others No No Y Y Y N/y + pyarmor 8 RFT Mode No No Y Y Y Y [#]_ + pyarmor 8 BCC Mode No No Y Y Y Y + pyarmor 8 others No No Y Y Y Y pyarmor-7 Y Y Y No No No =================== ===== ========= ========== ====== ====== ======= ============== From 8df81055735955afaa8144df4754c4bee4c2945a Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 13 Nov 2024 12:58:05 +0800 Subject: [PATCH 106/337] Update feature progress --- docs/ReleasePlan.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md index ae19aa55..01dbbde3 100755 --- a/docs/ReleasePlan.md +++ b/docs/ReleasePlan.md @@ -63,21 +63,26 @@ Status: released (2024-03-08) **9.0** -Status: developing, released before Nov. 1 as plan +Status: released (2024-11-10), still testing - Pyarmor.man package with web-ui to help users learning, using Pyarmor, and quickly find solutions when something is wrong - Add special Pyarmor CI License for CI/CD pipeline +- Support Python 3.13 (since v9.0.5) - Full test **9.1** -Status: not start +Status: developing -- Support Python 3.13 - Refine RFT mode to improve performance for big scripts - FLY mode, a simple RFT mode, the goal is high performace after obfuscation + +**9.x** + +Status: not start + - Convert current unsupported node types as far as possible for BCC mode -- Expert users could customize C sources generated by BCC mode +- Expert users could customize C sources generated by BCC mode (may cancel) - BCC mode support cross-platform (may not if too complex) (draft) - Obfuscate string constants in extension module "pyarmor_runtime" (draft) From 37e181d8ca2236fdfdedb72e456840b11b53b525 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 13 Nov 2024 17:49:10 +0800 Subject: [PATCH 107/337] Remove empty metavar from command help --- src/cli/__main__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 84cfa465..e2cd988e 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -395,7 +395,6 @@ def main_parser(): subparsers = parser.add_subparsers( title='The most commonly used pyarmor commands are', - metavar='' ) gen_parser(subparsers) From 6d3bcf5690251fd4caa6b0ca135c6ce23c635034 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 13 Nov 2024 19:53:19 +0800 Subject: [PATCH 108/337] Draft new feature: MCC mode --- docs/ReleasePlan.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md index 01dbbde3..a1fb3a5c 100755 --- a/docs/ReleasePlan.md +++ b/docs/ReleasePlan.md @@ -63,25 +63,26 @@ Status: released (2024-03-08) **9.0** -Status: released (2024-11-10), still testing +Status: released (2024-11-10), still testing, not stable - Pyarmor.man package with web-ui to help users learning, using Pyarmor, and quickly find solutions when something is wrong - Add special Pyarmor CI License for CI/CD pipeline - Support Python 3.13 (since v9.0.5) -- Full test **9.1** Status: developing -- Refine RFT mode to improve performance for big scripts -- FLY mode, a simple RFT mode, the goal is high performace after obfuscation +- Full testcases +- Refine RFT mode to improve performance for big scripts (same as MINI mode) +- MINI mode, a simple RFT mode, the goal is high performace after obfuscation **9.x** Status: not start -- Convert current unsupported node types as far as possible for BCC mode +- MCC mode, mini BCC mode, only transform part of function body to C code (draft) +- Convert unsupported node types as far as possible for BCC mode (may cancel) - Expert users could customize C sources generated by BCC mode (may cancel) - BCC mode support cross-platform (may not if too complex) (draft) - Obfuscate string constants in extension module "pyarmor_runtime" (draft) From 17c47722603946706f22c9f6684df3698a4dfd34 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 13 Nov 2024 20:04:44 +0800 Subject: [PATCH 109/337] Refine format --- docs/ReleasePlan.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md index a1fb3a5c..2f7605e3 100755 --- a/docs/ReleasePlan.md +++ b/docs/ReleasePlan.md @@ -1,7 +1,12 @@ -# Release Plan for Pyarmor 8+ +# Release Plan The scheduled features for Pyarmor 8+ +- [Pyarmor 8 (closed)](#plan-features-for-pyarmor-8) +- [Pyarmor 9 (working)](#plan-features-for-pyarmor-9) + +## Plan Features For Pyarmor 8 + **8.0** Status: released @@ -61,6 +66,8 @@ Status: released (2024-03-08) - Runtime key could be obfuscated - No patch `pyarmor_runtime.so` for Apple Silcon so Windows users could build obfuscated scripts for Apple Silcon +## Plan Features For Pyarmor 9 + **9.0** Status: released (2024-11-10), still testing, not stable From 5ce6337db861f8e67eff05fa09800ea6627257f9 Mon Sep 17 00:00:00 2001 From: Ilan Keshet Date: Thu, 21 Nov 2024 17:00:51 +0200 Subject: [PATCH 110/337] Fix typo in licenses.rst 'coutless' to 'countless' --- docs/licenses.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index b38e12e3..a33601a8 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -108,7 +108,7 @@ Terms of Use 3. One product one license - One product means one kind of product, not one copy of product. For example, Micorsoft Excel is one product, even it's installed on coutless devices + One product means one kind of product, not one copy of product. For example, Micorsoft Excel is one product, even it's installed on countless devices Each license has an unique number, the format is ``pyarmor-vax-xxxxxx``, which x stands for a digital. From 5a7f070e2e5f9e9c81fb7b52363a474153f1a2fd Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 25 Nov 2024 09:23:58 +0800 Subject: [PATCH 111/337] Support Python 3.13 --- src/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/README.rst b/src/README.rst index 3336028a..06383ec2 100644 --- a/src/README.rst +++ b/src/README.rst @@ -16,7 +16,7 @@ Key Features Support Platforms ----------------- -* Python 3.7~3.12 +* Python 3.7~3.13 * Windows * Many linuxs, include embedded systems * Apple Intel and Apple Silicon From 60919e08ae9810ebf1ca11e4a08cc2ffb9c004bb Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 26 Nov 2024 15:03:54 +0800 Subject: [PATCH 112/337] Change version to 9.0.7 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 636dc390..7e214ef1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.0' # The full version, including alpha/beta/rc tags. -release = '9.0.6' +release = '9.0.7' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 044139fc..fa2a1f7c 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.0.6' +__VERSION__ = '9.0.7' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 35c59c48..c273274a 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 0 -patch = 6 +patch = 7 ;; Compatible core version cli.core = 7.6.2 diff --git a/src/config.py b/src/config.py index 87e184a6..d8768cae 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.0.6' +version = '9.0.7' # The corresponding version of pytransform.so core_version = 'r52.6' From 25a42a70c16d1e91fcc2921ee5fc1b0449a295b8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 28 Nov 2024 16:08:24 +0800 Subject: [PATCH 113/337] Note there is rate limit for CI License --- docs/licenses.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/licenses.rst b/docs/licenses.rst index a33601a8..922034e9 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -122,6 +122,8 @@ Terms of Use Pyarmor Pro License still could be used in CI/CD pipeline for low volume (100 runs per month) + Pyarmor CI License has rate limit in CI/CD pipeline + In details check `Pyarmor EULA`_ .. seealso:: :doc:`Using Pyarmor Licenses ` @@ -217,4 +219,17 @@ Microsoft Office is not one product, because each product in Microsoft Office is Microsoft Word is one product, and Microsoft Word 2003,Word 2007 etc. are belong to one product Microsoft word. +Q & A +----- + +**I noticed that the pricing table lists "0 Maximum build devices," for the CI/CD plan which has left me a bit confused. Could you please clarify what this means?** + + It means CI License only works in CI/CD pipeline, can’t be used in local device. + +**If we are using the product in our CI/CD environment we can deploy it to unlimited number of kubernetes pods** + + If the product means the obfuscated scripts, Pyarmor has no any limitation on it. + + Pyarmor License is only apply to build machine in which to generate the obfuscated scripts. + .. include:: _common_definitions.txt From 8983df9046522dcfe1d4143b8a91f20bccc29db1 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 4 Dec 2024 10:15:33 +0800 Subject: [PATCH 114/337] Fix typo --- docs/licenses.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index 922034e9..6b57bd62 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -118,7 +118,7 @@ Terms of Use 5. Fair use - If you have purchased Pyarmor License for one product, but you have aother product, and the total revenue of the other project is less than 100 x Pyarmor License fee, you could rent this license in your aother product + If you have purchased Pyarmor License for one product, but you have another product, and the total revenue of the other project is less than 100 x Pyarmor License fee, you could rent this license in your aother product Pyarmor Pro License still could be used in CI/CD pipeline for low volume (100 runs per month) From 9164abc2a176ad59f9d9559a350ca19a8758f183 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 5 Dec 2024 10:47:49 +0800 Subject: [PATCH 115/337] Note how to select license type Explain how many license required --- docs/licenses.rst | 122 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/docs/licenses.rst b/docs/licenses.rst index 6b57bd62..10fdb55a 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -173,6 +173,8 @@ Open `Pyarmor Shopping Cart`_ in any web browser: https://jondy.github.io/paypal/index.html +All of these license are only for Python 3.7+, if need Python < 3.7, please purchase `old license ` + Refund policy ============= @@ -219,9 +221,129 @@ Microsoft Office is not one product, because each product in Microsoft Office is Microsoft Word is one product, and Microsoft Word 2003,Word 2007 etc. are belong to one product Microsoft word. +.. _select-license-type: + +Which license type is right for me +---------------------------------- + +All of the following licenses are only for Python 3.7+ + +.. list-table:: Table-2. Select Different Licenses + :widths: 40 12 12 12 12 12 + :header-rows: 1 + :stub-columns: 1 + + * - Condition + - Free + - Basic + - Pro + - Group + - CI + * - Less than 100 runs per month in CI/CD pipeline + - Y + - Y + - Y + - + - Y + * - More than 100 runs per month in CI/CD pipeline + - Y + - Y + - + - + - Y + * - Need offline obfuscation + - Y + - + - + - Y + - + * - Need irreversible obfuscation + - + - + - Y + - Y + - Y + * - Less than 100 runs per month in local dockers + - Y + - Y + - Y + - Y + - Y + * - More than 100 runs per month in local dockers + - Y + - Y + - + - Y + - Y + +.. _how-many-licenses-required: + +How many licenses are required +------------------------------ + +1. List all the products which are sold separately + + - If the sales revenue of the product is less than 100 x Pyarmor license fee, there is no need to list the product + - If no more than 2 products left, one license is OK + +2. How to tell diffenent product could be taken as one proudct in Pyarmor view + + Suppose there are 2 products left in step 1: A and B + + a. case 1, A and B could use one license + + - B includes whole A features + - The extra features in B is related to A features + + For example, Pyarmor Basic (A), Pyarmor Pro (B) + + b. case 2, A and B could use one license + + - B is a functional supplement for product A + + For example, A is CAD Editor, B is CAD Tool to convert CAD file to PDF file + + c. case 3, A and B need 2 licenses + + - B includes whole A features + - A features is very small than B features + + For example, A is a facial recognition product, B is an attendance management system that uses facial recognition functionality + + d. case 4, A and B need 2 licenses + + - The functions of B and A are almost independent + + For example, Microsoft Word (A), Micorsoft Excel (B) are 2 products, even they're belong to Micorsoft Office Suite + +3. Regard to shared backend system + + There are 2 product A and B, share one backend engineer C + + a. case 1: need obfuscate backend C, but frontend of A and B need not + + - one license for backend C is OK + + b. case 2: need obfuscate backend C, and the frontend of A and B + + - 2 licenses, one for A, another for B + - C need not new license, use any license for A or B + +4. Many products need use one license in technical + + If there are many products (in Pyarmor view) need use one Pyarmor license in development view + + - Purchase many licenses + - Only initial registration for each product with different license + - Use one of registeration file to generate the obfuscated scripts + Q & A ----- +**I have just started selling my product, do I can use Pyarmor trial version to product it** + +Before the value of sales exceed 100 x Pyarmor license fee, Pyarmor trial version can be used to obfuscate the product. After the value of sales exceed, it need purchase Pyarmor license. + **I noticed that the pricing table lists "0 Maximum build devices," for the CI/CD plan which has left me a bit confused. Could you please clarify what this means?** It means CI License only works in CI/CD pipeline, can’t be used in local device. From 8f725aab1c64237f22b693590cd909bf321a18d5 Mon Sep 17 00:00:00 2001 From: Ilan Keshet Date: Thu, 5 Dec 2024 10:40:41 +0200 Subject: [PATCH 116/337] Typo fix at solutions.rst 'firwire' to 'firewall' and port phrasing correction --- docs/reference/solutions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/solutions.rst b/docs/reference/solutions.rst index b3051668..7c82a6e5 100644 --- a/docs/reference/solutions.rst +++ b/docs/reference/solutions.rst @@ -121,7 +121,7 @@ If it's using :term:`Activation File` (`pyarmor-regcode-xxxx.txt`), make sure th >>> print(res.read()) b'NO:missing parameter' -If it raises exception or return something else, it’s firewall problem, please configure firwire to allow Python interpreter to visit pyarmor.dashingsoft.com and port 80 or 443 +If it raises exception or return something else, it’s firewall problem, please configure firewall to allow Python interpreter to visit pyarmor.dashingsoft.com at port 80 or 443 If it returns as above, but still failed to register, report issue with license no. like `pyarmor-vax-5068` From 67be571c0ea6659fa815dc6e7f3b53631cc355e9 Mon Sep 17 00:00:00 2001 From: Ilan Keshet Date: Thu, 5 Dec 2024 10:50:12 +0200 Subject: [PATCH 117/337] Fix various typo fixes in solutions.rst --- docs/reference/solutions.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/reference/solutions.rst b/docs/reference/solutions.rst index 7c82a6e5..aaaf8890 100644 --- a/docs/reference/solutions.rst +++ b/docs/reference/solutions.rst @@ -24,7 +24,7 @@ Bootstrap failed 2. Check extension `pytransform3` is exact for this Python and platform - Test it by Python interperter: + Test it by Python interpreter: .. code-block:: bash @@ -88,7 +88,7 @@ Bootstrap failed $ export PYARMOR_PLATFORM=linux.loongarch64 $ pyarmor gen foo.py -4. If this platform is supported, try to upgrade Python interprerter to latest patch version. For example, upgrade Python 3.11.0b2 to 3.11.9 +4. If this platform is supported, try to upgrade Python interpreter to latest patch version. For example, upgrade Python 3.11.0b2 to 3.11.9 Registration Failed ------------------- @@ -111,7 +111,7 @@ If it's using :term:`Activation File` (`pyarmor-regcode-xxxx.txt`), make sure th Otherwise check network configuration -6. Check Python interpreter by the following commands (If there are many Python installed, make sure this Python interperter is used to execute Pyarmor) +6. Check Python interpreter by the following commands (If there are many Python installed, make sure this Python interpreter is used to execute Pyarmor) .. code-block:: bash @@ -178,7 +178,7 @@ Please check above section `Registration Failed` Packing failed -------------- -1. Try to pack the original script by PyInstaller directly first, mkae sure it works and the final bundle works +1. Try to pack the original script by PyInstaller directly first, make sure it works and the final bundle works 2. Check :doc:`../topic/repack` .. _target device: @@ -203,7 +203,7 @@ Bootstrap failed If it doesn't work, try to use :option:`-i` or :option:`--prefix` to generate the obfuscated scripts again to fix it -3. Try to upgrade Python interprerter to latest patch version. For example, upgrade Python 3.11.0b2 to 3.11.9 +3. Try to upgrade Python interpreter to latest patch version. For example, upgrade Python 3.11.0b2 to 3.11.9 **If target device is different from build device** @@ -251,7 +251,7 @@ Failed to run obfuscated scripts 2. If it doesn't work, try to obfuscate one simple script, check it works or not -3. Add some print statement in the problem script, and get one script as simple as possible to reproduce the problem. It's better only use Python system packages. If really need third-parth library, check :doc:`../how-to/third-party` first +3. Add some print statement in the problem script, and get one script as simple as possible to reproduce the problem. It's better only use Python system packages. If really need third-party library, check :doc:`../how-to/third-party` first 4. Report issue with necessary information @@ -272,7 +272,7 @@ Check solutions in :ref:`using bccmode` Failed to run the packed obfuscated scripts ------------------------------------------- -1. Do not pack the sciprt, just use same options to obfuscate the script, and run the obfuscated script in target device, make sure it works, otherwise check solutions in above section +1. Do not pack the script, just use same options to obfuscate the script, and run the obfuscated script in target device, make sure it works, otherwise check solutions in above section 2. Do not obfuscate the scripts, pack the original script by PyInstaller directly, and execute the final executable in target device, make sure it works. Otherwise check PyInstaller_ documentation to find solutions @@ -291,10 +291,10 @@ Darwin Apple Silicon may need codesign if pyarmor or the obfuscated script can't Download https://pyarmor.dashingsoft.com/downloads/tools/cygcheck.zip and unzip it - Or get it from offical website + Or get it from official website - Open https://cygwin.com/mirrors.html - - Select one mirror, enter pathes `x86_64/release/cygwin/` + - Select one mirror, enter paths `x86_64/release/cygwin/` - Download the latest package `cygwin-3.5.3-1.tar.xz` - Extract `cygcheck.exe` from this file by `tar xJf cygwin-3.5.3-1.tar.xz` From de75896e7f275ebdc91bddec22151fec1a2280d1 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 5 Dec 2024 20:55:02 +0800 Subject: [PATCH 118/337] Refine doc --- docs/licenses.rst | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index 10fdb55a..045b9c41 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -288,33 +288,36 @@ How many licenses are required 2. How to tell diffenent product could be taken as one proudct in Pyarmor view - Suppose there are 2 products left in step 1: A and B + Suppose there are 2 products in step 1: X and Y - a. case 1, A and B could use one license + a. case 1, X and Y could use one license - - B includes whole A features - - The extra features in B is related to A features + - Y includes whole X features + - The extra features of Y is related to X features - For example, Pyarmor Basic (A), Pyarmor Pro (B) + For example, Pyarmor Basic (X), Pyarmor Pro (Y) could use one license because - b. case 2, A and B could use one license + - Pyarmor Pro includes all the features of Pyarmor Basic + - The extra features of Pyarmor Pro is irreversible obfuscation which is an enhancement of Pyarmor Basic - - B is a functional supplement for product A + b. case 2, X and Y need 2 licenses - For example, A is CAD Editor, B is CAD Tool to convert CAD file to PDF file + - Y includes whole X features + - But X features is very small in the Y features - c. case 3, A and B need 2 licenses + For example, X is a facial recognition product, B is an attendance management system that uses facial recognition functionality - - B includes whole A features - - A features is very small than B features + c. case 3, X and Y could use one license - For example, A is a facial recognition product, B is an attendance management system that uses facial recognition functionality + - Y is a functional supplement for product X - d. case 4, A and B need 2 licenses + For example, X is CAD Editor, Y is CAD Tool which is used to convert CAD file to PDF file - - The functions of B and A are almost independent + d. case 4, X and Y need 2 licenses - For example, Microsoft Word (A), Micorsoft Excel (B) are 2 products, even they're belong to Micorsoft Office Suite + - The functions of X and Y are almost independent + + For example, Microsoft Word (X), Micorsoft Excel (Y) are 2 products, even they're belong to Micorsoft Office Suite 3. Regard to shared backend system @@ -331,11 +334,11 @@ How many licenses are required 4. Many products need use one license in technical - If there are many products (in Pyarmor view) need use one Pyarmor license in development view + If there are many products (in Pyarmor view) need use one Pyarmor license in development view, it should - Purchase many licenses - - Only initial registration for each product with different license - - Use one of registeration file to generate the obfuscated scripts + - Register each product with different license once + - Use one of registeration file to generate the obfuscated scripts for all the products Q & A ----- From 74a383b775deb5a32541f3e4e2188b962081c10e Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 12 Dec 2024 09:24:26 +0800 Subject: [PATCH 119/337] Note CI License may not work if the runner has its own disk even in CI/CD pipeline --- docs/how-to/register.rst | 2 ++ docs/licenses.rst | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 8f080f39..8fc17c2d 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -118,6 +118,8 @@ Notes :term:`Pyarmor CI` License doesn't work in local device + Even in the CI/CD pipeline, :term:`Pyarmor CI` License also doesn't work in the runner which has its own disk. If the runner is not docker container, use :term:`Pyarmor Pro` License instead. + .. seealso:: :doc:`ci` .. _check device for group license: diff --git a/docs/licenses.rst b/docs/licenses.rst index 045b9c41..57cfdc1b 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -83,7 +83,13 @@ There are some limitations in free version, for example, can't obfuscate big scr .. [#] Offline build: the build device need not be online to verify Pyarmor License .. [#] Maximum devices could install Pyarmor, each docker run will be taken as one new build device, CI license only works in CI/CD pipeline .. [#] Unlimited local dockers: run docker container in local machine, may be offline or in private network -.. [#] Work in CI/CD pipeline: it need special option to register Pyarmor in CI/CD pipeline +.. [#] Work in CI/CD pipeline: it need special option to register Pyarmor in CI/CD pipeline. + +.. important:: + + CI license doesn't work in the runner which has its own disk. + + If the runner is not docker container, use Pro license instead. .. important:: From 4a0e232109a58182afe7f006bc8560a6b1bb3ae4 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 15 Dec 2024 10:46:51 +0800 Subject: [PATCH 120/337] Refine error message for old license --- src/cli/register.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index b1f21194..b569d175 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -356,7 +356,10 @@ def _get_docker_hostname(self): def _get_machine_id(self, devflag=11): from .core import Pytransform3 - return Pytransform3.get_hd_info(devflag) + try: + return Pytransform3.get_hd_info(devflag) + except SystemError: + return b'unknown-machine' def generate_group_device(self, devid): from datetime import datetime @@ -792,7 +795,9 @@ def request_ci_regfile(self, regfile, rev=2): with ZipFile(regfile, 'r') as f: if 'reg.info' not in f.namelist(): - logger.error('missing reg.info in regfile') + logger.error('missing reg.info in regfile, ' + 'this license may be out of date, ' + 'please check Pyarmor upgrade notes') raise CliError('can not request CI license') reginfo = json_loads(f.read('reg.info')) From b97bb43aa2694da19883159e175e8bfe7712a667 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 16 Dec 2024 15:06:46 +0800 Subject: [PATCH 121/337] Check product length --- src/cli/register.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cli/register.py b/src/cli/register.py index b569d175..67bd8cd9 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -160,6 +160,8 @@ def _get_old_rcode(self): def regurl(self, ucode, product=None, rcode=None, prepare=False): url = self.ctx.cfg['pyarmor']['regurl'] % ucode if product: + if len(product) > 60: + raise CliError('too long product name (length > 60)') url += '&product=' + \ urlsafe_b64encode(product.encode('utf-8')).decode() if rcode: From e261caea626a0e208d3bab711fc32b9c752849cd Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 17 Dec 2024 21:16:33 +0800 Subject: [PATCH 122/337] Refine code --- src/cli/register.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index 67bd8cd9..0ee04d34 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -361,7 +361,8 @@ def _get_machine_id(self, devflag=11): try: return Pytransform3.get_hd_info(devflag) except SystemError: - return b'unknown-machine' + from time import time + return b'u-machine-id:%020.6f' % time() def generate_group_device(self, devid): from datetime import datetime From a5b6b8bcfa7d303b87f0a8ff6fb2ac2acebfc6bb Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 19 Dec 2024 09:24:26 +0800 Subject: [PATCH 123/337] Update release plan Cancel some features Add some features --- docs/ReleasePlan.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md index 2f7605e3..415929eb 100755 --- a/docs/ReleasePlan.md +++ b/docs/ReleasePlan.md @@ -84,14 +84,21 @@ Status: developing - Refine RFT mode to improve performance for big scripts (same as MINI mode) - MINI mode, a simple RFT mode, the goal is high performace after obfuscation -**9.x** +**9.2** Status: not start -- MCC mode, mini BCC mode, only transform part of function body to C code (draft) -- Convert unsupported node types as far as possible for BCC mode (may cancel) -- Expert users could customize C sources generated by BCC mode (may cancel) -- BCC mode support cross-platform (may not if too complex) (draft) -- Obfuscate string constants in extension module "pyarmor_runtime" (draft) +- ECC mode, mini BCC mode, only transform part of function body to C code (draft) +- VMC mode, a simple ECC mode, convert function body to VM, not real C code +- Convert unsupported node types as far as possible for BCC mode (cancel) +- Expert users could customize C sources generated by BCC mode (cancel) +- BCC mode support cross-platform (may not if too complex) (cancel) +- Obfuscate string constants in extension module "pyarmor_runtime" (cancel) + +**9.x** + +Status: plan, released on Dec., 2025, 2 months after Python 3.14 is released + +- Support Python 3.14 which will be released on 2025-10-01 as plan -Pyarmor status will be stable by the end of 2024 (Dec. 31, 2024) +Pyarmor features will be stable around Dec. 31, 2024 From 51ff8a41f1a2bd3a714a97d5718df27852604a91 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 20 Dec 2024 06:23:59 +0800 Subject: [PATCH 124/337] Fix typos --- docs/ReleasePlan.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md index 415929eb..511d98dc 100755 --- a/docs/ReleasePlan.md +++ b/docs/ReleasePlan.md @@ -82,14 +82,17 @@ Status: developing - Full testcases - Refine RFT mode to improve performance for big scripts (same as MINI mode) -- MINI mode, a simple RFT mode, the goal is high performace after obfuscation +- MINI mode, a simple RFT mode, the goal is high performance after obfuscation **9.2** Status: not start - ECC mode, mini BCC mode, only transform part of function body to C code (draft) -- VMC mode, a simple ECC mode, convert function body to VM, not real C code +- VMC mode, simple ECC mode, convert function body to VM, not real C code + +The following features are cancelled because ECC/VMC mode are introduced: + - Convert unsupported node types as far as possible for BCC mode (cancel) - Expert users could customize C sources generated by BCC mode (cancel) - BCC mode support cross-platform (may not if too complex) (cancel) From 12af064b99787cac56fc9c4c436e4976bd915c44 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 21 Dec 2024 09:13:20 +0800 Subject: [PATCH 125/337] Refine date format --- docs/ReleasePlan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md index 511d98dc..9555611b 100755 --- a/docs/ReleasePlan.md +++ b/docs/ReleasePlan.md @@ -100,7 +100,7 @@ The following features are cancelled because ECC/VMC mode are introduced: **9.x** -Status: plan, released on Dec., 2025, 2 months after Python 3.14 is released +Status: plan, released on 2025-12-01, 2 months after Python 3.14 is released - Support Python 3.14 which will be released on 2025-10-01 as plan From 323b7ec756450f0884c897e3a878cee5f411286d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 21 Dec 2024 12:46:38 +0800 Subject: [PATCH 126/337] Add threading testcases --- tests.8/accept_test.py | 23 +++++++++++++++++++++++ tests.8/samples/mp.py | 18 ++++++++++++++++++ tests.8/samples/td.py | 21 +++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 tests.8/samples/mp.py create mode 100644 tests.8/samples/td.py diff --git a/tests.8/accept_test.py b/tests.8/accept_test.py index d4d6287b..344c4ef5 100755 --- a/tests.8/accept_test.py +++ b/tests.8/accept_test.py @@ -362,6 +362,29 @@ def test_mp_with_bcc(self): 'hello bob', ]])) + def test_thread(self): + args = ['g', 'samples/td.py'] + self.pyarmor_gen(args) + rc, stdout, stderr = self.assert_python_ok('dist/td.py') + lines = [x.strip() for x in stdout.splitlines()] + self.assertTrue(all([x.encode() in lines for x in [ + 'module name: __main__', + 'function f', + 'hello bob', + ]])) + + @only_protest + def test_thread_with_bcc(self): + args = ['g', '--enable-bcc', 'samples/td.py'] + self.pyarmor_gen(args) + rc, stdout, stderr = self.assert_python_ok('dist/td.py') + lines = [x.strip() for x in stdout.splitlines()] + self.assertTrue(all([x.encode() in lines for x in [ + 'module name: __main__', + 'function f', + 'hello bob', + ]])) + if __name__ == '__main__': logging.getLogger().addHandler(logging.NullHandler()) diff --git a/tests.8/samples/mp.py b/tests.8/samples/mp.py new file mode 100644 index 00000000..c232d0ba --- /dev/null +++ b/tests.8/samples/mp.py @@ -0,0 +1,18 @@ +from multiprocessing import Process +import os + +def info(title): + print(title) + print('module name:', __name__) + print('parent process:', os.getppid()) + print('process id:', os.getpid()) + +def f(name): + info('function f') + print('hello', name) + +if __name__ == '__main__': + info('main line') + p = Process(target=f, args=('bob',)) + p.start() + p.join() diff --git a/tests.8/samples/td.py b/tests.8/samples/td.py new file mode 100644 index 00000000..2450ebd3 --- /dev/null +++ b/tests.8/samples/td.py @@ -0,0 +1,21 @@ +from threading import Thread + + +def info(title): + print(title) + print('module name:', __name__) + + +def f(name): + info('function f') + print('hello', name) + + +if __name__ == '__main__': + tlist = [] + for i in range(10): + p = Thread(target=f, args=('bob',)) + p.start() + tlist.append(p) + for p in tlist: + p.join() From 42be93eab5a7216d5a68f46c77914a823159cd78 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 25 Dec 2024 10:25:06 +0800 Subject: [PATCH 127/337] Split pyarmor.man functions to learning system --- docs/ReleasePlan.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md index 9555611b..2bfbb21d 100755 --- a/docs/ReleasePlan.md +++ b/docs/ReleasePlan.md @@ -70,9 +70,9 @@ Status: released (2024-03-08) **9.0** -Status: released (2024-11-10), still testing, not stable +Status: released (2024-11-10) -- Pyarmor.man package with web-ui to help users learning, using Pyarmor, and quickly find solutions when something is wrong +- Pyarmor.man package to find solutions quickly when something is wrong - Add special Pyarmor CI License for CI/CD pipeline - Support Python 3.13 (since v9.0.5) @@ -88,7 +88,7 @@ Status: developing Status: not start -- ECC mode, mini BCC mode, only transform part of function body to C code (draft) +- ECC mode, mini BCC mode, only transform part of function body to C code - VMC mode, simple ECC mode, convert function body to VM, not real C code The following features are cancelled because ECC/VMC mode are introduced: @@ -98,7 +98,13 @@ The following features are cancelled because ECC/VMC mode are introduced: - BCC mode support cross-platform (may not if too complex) (cancel) - Obfuscate string constants in extension module "pyarmor_runtime" (cancel) -**9.x** +**9.3** + +Status: not start + +- Learning system to help users learning and using Pyarmor + +**9.4** Status: plan, released on 2025-12-01, 2 months after Python 3.14 is released From e4c35eb2e1a632ce083698e7a03f30c564d94be7 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 30 Dec 2024 19:46:05 +0800 Subject: [PATCH 128/337] Change version to 9.0.8 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7e214ef1..b2abf94b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.0' # The full version, including alpha/beta/rc tags. -release = '9.0.7' +release = '9.0.8' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index fa2a1f7c..a01e505a 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.0.7' +__VERSION__ = '9.0.8' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index c273274a..8d20617e 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 0 -patch = 7 +patch = 8 ;; Compatible core version cli.core = 7.6.2 diff --git a/src/config.py b/src/config.py index d8768cae..c7ebee94 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.0.7' +version = '9.0.8' # The corresponding version of pytransform.so core_version = 'r52.6' From 1770b063d51b490f55b4f2bc313f4961e60901d8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 2 Jan 2025 20:53:07 +0800 Subject: [PATCH 129/337] Add new command 'init', 'build', 'env' --- src/cli/__main__.py | 14 +- src/cli/command.py | 484 ++++++++++++++++++++++++++++++++++++++++++++ src/cli/model.py | 442 ++++++++++++++++++++++++++++++++++++++++ src/cli/shell.py | 462 +++++++++++++++++++++++++++++++++++++++--- 4 files changed, 1366 insertions(+), 36 deletions(-) create mode 100644 src/cli/command.py create mode 100644 src/cli/model.py diff --git a/src/cli/__main__.py b/src/cli/__main__.py index e2cd988e..8608ee52 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -32,6 +32,7 @@ from .plugin import Plugin from .generate import Builder from .bootstrap import check_prebuilt_runtime_library +from .command import Commander def _cmd_gen_key(builder, options): @@ -369,7 +370,7 @@ def cmd_man(ctx, args): check_call([sys.executable, '-m', 'pyarmor.man.shell']) -def main_parser(): +def main_parser(cmd): parser = argparse.ArgumentParser( prog='pyarmor', fromfile_prefix_chars='@', @@ -399,8 +400,12 @@ def main_parser(): gen_parser(subparsers) reg_parser(subparsers) - cfg_parser(subparsers) + cmd.env_parser(subparsers) + cmd.init_parser(subparsers) + cmd.build_parser(subparsers) + + cfg_parser(subparsers) man_parser(subparsers) return parser @@ -748,10 +753,11 @@ def get_home_paths(args): def main_entry(argv): - parser = main_parser() + cmd = Commander() + parser = main_parser(cmd) args = parser.parse_args(argv) - ctx = Context(*get_home_paths(args)) + ctx = cmd.ctx = Context(*get_home_paths(args)) log_settings(ctx, args) x, y = sys.version_info[:2] diff --git a/src/cli/command.py b/src/cli/command.py new file mode 100644 index 00000000..554b3a32 --- /dev/null +++ b/src/cli/command.py @@ -0,0 +1,484 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# +############################################################# +# # +# Copyright @ 2024 Dashingsoft corp. # +# All rights reserved. # +# # +# pyarmor # +# # +# Version: 9.1.0 - # +# # +############################################################# +# +# +# @File: pyarmor/cli/commander.py +# +# @Author: Jondy Zhao(pyarmor@163.com) +# +# @Create Date: Tue Nov 12 16:38:51 CST 2024 +# +import argparse +import configparser +import shlex + +from os import makedirs +from os.path import abspath, exists, join as joinpath, relpath + +from . import logger +from .project import Project +from .shell import PyarmorShell + + +class Commander: + + def __init__(self, ctx=None): + self.ctx = ctx + + def init_parser(self, subparsers): + parser = subparsers.add_parser( + 'init', + aliases=['i'], + formatter_class=argparse.RawDescriptionHelpFormatter, + description='init project in current path', + help='init project in current path' + ) + + parser.add_argument( + '-s', '--src', metavar='PATH', default='', + help='where to find scripts, modules and packages' + ) + parser.add_argument( + '-e', '--entry', dest='scripts', metavar='FILE', + action='append', + help='project entry scripts' + ) + parser.add_argument( + '-m', '--module', metavar='FILE', + dest='modules', action='append', + help='append extra module' + ) + parser.add_argument( + '-p', '--package', metavar='PATH', + dest='packages', action='append', + help='append extra package' + ) + + parser.add_argument( + '-x', '--exclude', metavar='PATTERN', + dest='excludes', action='append', + help='exclude file or path from src' + ) + + parser.add_argument( + '-r', '--recursive', action='store_true', + help='search modules and packages recursively' + ) + parser.add_argument( + '-C', '--clean', action='store_true', + help='remove old project information before init' + ) + + parser.set_defaults(func=self.cmd_init) + + def env_parser(self, subparsers): + parser = subparsers.add_parser( + 'env', + aliases=['environ', 'e'], + formatter_class=argparse.RawDescriptionHelpFormatter, + description='check and set Pyarmor environments', + help='check and set Pyarmor environments' + ) + + # parser.add_argument( + # '-i', '--interactive', action='store_true', + # help='enable interactive mode' + # ) + + group = parser.add_argument_group( + 'select domain (default: project)' + ).add_mutually_exclusive_group() + group.add_argument( + '-l', '--local', dest='domain', default='local', + action="store_const", const='local', + help='enter local domain' + ) + group.add_argument( + '-g', '--global', dest='domain', default='local', + action="store_const", const='global', + help='enter global domain' + ) + group.add_argument( + '-p', '--project', dest='domain', default='local', + action="store_const", const='project', + help='enter project domain' + ) + + parser.add_argument( + 'exprs', metavar='VERB', nargs='*', + help='change option by info/ls/set/reset/push/pop' + ) + + parser.set_defaults(func=self.cmd_env) + + def build_parser(self, subparsers): + parser = subparsers.add_parser( + 'build', + aliases=['b'], + formatter_class=argparse.RawTextHelpFormatter, + help='obfuscate all the scripts in the project' + ) + + group = parser.add_argument_group( + 'select build target (default: std)' + ).add_mutually_exclusive_group() + # group.add_argument( + # '--std', dest='target', default='std', + # action="store_const", const='std', + # help='genetate standard obfuscated scripts' + # ) + # group.add_argument( + # '--ecc', dest='target', default='std', + # action="store_const", const='ecc', + # help='genetate scripts with embedded C code' + # ) + # group.add_argument( + # '--vmc', dest='target', default='std', + # action="store_const", const='vmc', + # help='genetate scripts with vm C code' + # ) + group.add_argument( + '--mini', dest='target', default='std', + action="store_const", const='mini', + help='genetate high performance scripts' + ) + group.add_argument( + '--rft', dest='target', default='std', + action="store_const", const='rft', + help='only refactor scripts' + ) + group.add_argument( + '--list', dest='target', default='std', + action="store_const", const='list', + help='list project scripts, modules and packages' + ) + + # parser.add_argument( + # '--pack', metavar='MODE', + # help='specify pack mode, onefile or onedir' + # ) + + # sgroup = parser.add_argument_group( + # title='these options only for --std target', + # ) + # sgroup.add_argument( + # '--restrict', + # default=1, choices=(0, 1, 2), + # help='set restrict mode (default: 1)' + # ) + # sgroup.add_argument( + # '--assert-import', action="store_true", + # help='check imported module is obfuscated' + # ) + # sgroup.add_argument( + # '--assert-call', action="store_true", + # help='check function is obfuscated before call' + # ) + + parser.add_argument( + '-O', '--output', metavar='PATH', + help='output path, default is "dist"' + ) + + # parser.add_argument( + # 'project', metavar='PATH', nargs='?', + # help='project path (default: current path)' + # ) + + parser.set_defaults(func=self.cmd_build) + + def main_parser(self): + parser = argparse.ArgumentParser( + prog='pyarmor', + fromfile_prefix_chars='@', + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + '-v', '--version', action='store_true', + help='show version information and exit' + ) + parser.add_argument( + '-q', '--silent', action='store_true', + help='suppress all normal output' + ) + + subparsers = parser.add_subparsers( + title='The most commonly used pyarmor commands are', + ) + + self.env_parser(subparsers) + self.init_parser(subparsers) + self.build_parser(subparsers) + + return parser + + def run(self, argv): + parser = self.main_parser() + args = parser.parse_args(argv) + if hasattr(args, 'func'): + args.func(args) + else: + parser.print_help() + + def cmd_init(self, args): + logger.debug('init %s', args) + ctx = self.ctx + + sep = ',' + cfgsep = ' ' + data = {} + + cfg = configparser.ConfigParser( + empty_lines_in_values=False, + interpolation=configparser.ExtendedInterpolation(), + ) + cfg.read([ctx.local_config], encoding=ctx.encoding) + + sectname = 'project' + if args.clean: + logger.info('clean old project') + if cfg.has_section(sectname): + cfg.remove_section(sectname) + + if cfg.has_section(sectname): + logger.info('change project settings') + else: + cfg.add_section(sectname) + + sect = cfg[sectname] + if args.src: + src = abspath(args.src) + if not sect.get('src'): + sect['src'] = src + elif src != sect['src']: + raise RuntimeError( + 'project has another src, ' + 'use option "-C" to fix this issue' + ) + else: + src = sect.get('src') + if not src: + src = abspath('.') + sect['src'] = src + + if not exists(src): + raise RuntimeError('no found src "%s"' % src) + + def format_path(plist, raw=False): + r = [] + for x in sep.join(plist).split(sep): + x = x.strip() + r.append(x if raw else relpath(abspath(x), src)) + return r + + if args.scripts: + data['scripts'] = format_path(args.scripts) + + if args.excludes: + data['excludes'] = format_path(args.excludes, raw=1) + + if args.modules: + data['modules'] = format_path(args.modules) + + if args.packages: + data['packages'] = format_path(args.packages) + + if args.recursive: + data['recursive'] = 1 + + for key, value in data.items(): + old = sect.get(key) + if old is not None: + logger.info('overwrite %s: %s', key, old) + if isinstance(value, (tuple, list)): + value = cfgsep.join([ + x.replace(' ', '%20%') for x in value + ]) + sect[key] = value + + logger.info('project information') + for key, value in cfg.items(sectname): + if key in ('src',): + value = relpath(value) + logger.info('%-20s: %s', key, value) + + makedirs(ctx.local_path, exist_ok=True) + with open(ctx.local_config, 'w') as f: + cfg.write(f) + logger.info('project saved') + + def cmd_build(self, args): + """Obfuscate all scripts, modules and packages in project + + Show project information: + + pyarmor i + + Generate plain scripts which no extra extension, the final + scripts could be taken as input for Nuitka, Cython etc.: + + pyarmor build --rft + + Generate high performance scripts: + + pyarmor build --mini + + Generate standard obfuscated scirpts:: + + pyarmor build --std + + Generate standard obfuscated scirpts with extra options:: + + pyarmor build --std --assert-import --restrict 2 + + """ + logger.debug('build %s', args) + ctx = self.ctx + cfg = configparser.ConfigParser( + empty_lines_in_values=False, + interpolation=configparser.ExtendedInterpolation(), + ) + cfg.read([ctx.local_config], encoding=ctx.encoding) + + sectname = 'project' + if not cfg.has_section(sectname): + raise RuntimeError('no project information') + + cfgdata = dict(cfg.items(sectname)) + logger.debug('project: %s', cfgdata) + + project = Project(ctx) + logger.info('load project ...') + project.load(cfgdata) + logger.info('load project end') + + if args.target == 'list': + logger.info('project src: %s', relpath(project.src)) + logger.info('project scripts:') + for x in project.scripts: + logger.info(' %s', project.relsrc(x.abspath)) + logger.info('project modules:') + for x in project.modules: + logger.info(' %s', project.relsrc(x.abspath)) + logger.info('project packages:') + for x in project.packages: + logger.info(' %s', x.name) + mlist = [m.abspath for m in project.iter_module()] + mlist.sort() + for s in mlist: + logger.info(' %s', project.relsrc(s)) + return + + logger.info('build target %s ...', args.target) + output = args.output if args.output else 'dist' + self._build(project, args.target, output) + logger.info('build target %s end', args.target) + + def _build(self, project, target, output): + from .rftbuild import analysis_project, build_project + analysis_project(project) + build_project(project, target, output) + + def cmd_env(self, args): + """Check and change pyarmor settings + + Enter interactive mode: + + pyarmor env + + Show project src + + pyarmor env get src + + Change project src + + pyarmor env set src ../src + + Append project excludes + + pyarmor env push excludes "test*" + + Remove project excludes + + pyarmor env pop excludes "test*" + + Clear project excludes + + pyarmor env reset excludes + + """ + logger.debug('env %s', args) + logger.info('enter domain: %s', args.domain) + shell = PyarmorShell(self.ctx, domain=args.domain) + + if args.exprs: + exprs = args.exprs + n = len(exprs) + if n < 2: + logger.error('missing parameters') + return + + verb = exprs[0] + opt = exprs[1] + + i = opt.find(':') + if i > 0: + section, opt = opt[:i], opt[i+1:] + shell.do_cd(section) + + if verb == 'reset': + shell.do_reset(opt) + elif verb == 'get': + shell.do_get(opt) + elif verb == 'info': + shell.do_info(opt) + elif n < 3: + logger.error('missing parameters') + return + else: + arg = shlex.join([opt] + exprs[2:]) + if verb == 'pop': + shell.do_pop(arg) + elif verb == 'push': + shell.do_push(arg) + elif verb == 'set': + shell.do_set(arg) + else: + logger.error('unknown verb "%s"', verb) + return + + shell.save() + + else: + shell.cmdloop() + + +def test_main(args, log=False): + import logging.config + from os.path import expanduser + + from .context import Context + + home = joinpath('~', '.pyarmor') + home = abspath(expanduser(home)) + + ctx = Context(home) + if log: + logging.config.fileConfig(ctx.default_config) + Commander(ctx).run(args) + + +if __name__ == '__main__': + import sys + test_main(sys.argv[1:]) diff --git a/src/cli/model.py b/src/cli/model.py new file mode 100644 index 00000000..eef8fe0d --- /dev/null +++ b/src/cli/model.py @@ -0,0 +1,442 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# +############################################################# +# # +# Copyright @ 2024 - Dashingsoft corp. # +# All rights reserved. # +# # +# Pyarmor # +# # +# Version: 9.1.0 - # +# # +############################################################# +# +# +# @File: cli/model.py +# +# @Author: Jondy Zhao (pyarmor@163.com) +# +# @Create Date: Sun Dec 8 07:36:16 CST 2024 +# +from textwrap import dedent + + +class Item: + + def __init__(self, name, cls=None, require=False, many=False): + self.name = name + self.cls = cls + self.require = require + self.many = many + + +class Option: + + def __init__(self, name, parent=None, model=None): + self.name = name + self.parent = parent + self.model = model + self._value = None + + @property + def value(self): + if self._value is None: + self._value = self.parent.fetch(self.name) + if self._value is None: + return self.model.value + return self._value + + @value.setter + def value(self, data: str): + if data: + self.check(data) + self._value = data + self.parent.store(self.name, data) + else: + self.reset() + + def check(self, data: str): + pass + + def hints(self): + return '' + + def usage(self): + fmt = '%-20s: %s' + hints = [self.hints()] + hints.extend([(fmt % x) for x in self.model.hints()]) + if getattr(self, 'CHOICES', None): + values = ', '.join(self.CHOICES) + hints.append(fmt % ('Available values', values)) + hints.append('') + return '\n'.join(hints) + + def push(self, data: str): + items = self.value.splitlines() if self.value else [] + value = data.strip() + if value not in items: + items.append(value) + self.value = '\n'.join(items) + + def pop(self, data: str): + items = self.value.splitlines() if self.value else [] + try: + items.remove(data.strip()) + except ValueError: + pass + else: + self.value = '\n'.join(items) + + def reset(self): + """Clear option settings""" + self.parent.remove(self.name) + self._value = None + + +class OptionModel: + + def __init__(self, name, cls=None, **kwargs): + self.name = name + self.cls = cls + self.kwargs = kwargs + + def factory(self, parent): + Cls = globals().get(self.cls, Option) + return Cls(self.name, parent=parent, model=self) + + @property + def many(self): + """This option may has many values""" + return self.kwargs.get('many', False) + + @property + def value(self): + """The default value for this option""" + return self.kwargs.get('value') + + def hints(self): + return [('Type', self.cls), ('Many', self.many)] + + +class PathOption(Option): + pass + + +class PkgPathOption(PathOption): + + def hints(self): + return dedent("""\ + It could be relative path or absolute path. + + If package name is different from last path, use suffix format "path@pkgname" + """) + + +class FileOption(PathOption): + pass + + +class BoolOption(Option): + + CHOICES = '0', '1' + + def check(self, data: str): + if data not in self.CHOICES: + raise ValueError('invalid %s' % repr(data)) + + +class EnumOption(Option): + + def check(self, data: str): + if data not in self.CHOICES: + raise ValueError('invalid %s' % repr(data)) + + +class TextOption(Option): + pass + + +class NameOption(TextOption): + pass + + +class PatternOption(TextOption): + """re or fnmatch pattern""" + pass + + +class ListOption(Option): + pass + + +class DictOption(Option): + pass + + +class RftAttrEnum(EnumOption): + """When don't know how to rename attribute + + - ask: query user interactively + - log: log it but no rename + - yes: always rename + - no: do thing + - err: raise error quit + """ + + CHOICES = 'ask', 'log', 'yes', 'no', 'err' + + +class RftArgEnum(EnumOption): + """How to rename argument + + - 0: no rename arguments + - 1: rename all arguments + - min: only rename posonly, vararg and kwarg + - !keyword: no rename keyword only arguments + - !default: no rename argument which has default and kwarg + """ + + CHOICES = '0', '1', '2', '3', 'no', 'min', '!kw', 'all' + + +class RftNamePattern(TextOption): + """Match ast.Name in ast.Tree + + Each pattern includes 2 parts: + + - ModulePattern: fnmatchcase pattern, match module + + - NamePattern: fnmatchcase pattern, match function/class/name + """ + pass + + +class RftAttrFilter(ListOption): + """Filter ast.Attribute transformed to setattr/getattr + + Each ruler include 3 patterns + + - ModulePattern: fnmatchcase pattern, match module + + - ScopePattern: fnmatchcase pattern, match function/class + + - AttributePattern: fnmatchcase pattern, match attribute + + a: match any attribute "a" + b.a: match only "b.a" + a*: match any attribute startswith "a" + b.a*: match any "b" attribute which startswith "a" + b*.a: match any attribute "a" in parent startswith "b" + ().a: parent is function call + [].a: parent is subscript + + *.a: same as "a" + """ + pass + + +SECTIONS = { + 'pyarmor': [], + 'finder': [], + 'builder': [], + 'pack': [], + + # Group: filter + 'assert.import': [], + 'assert.call': [], + 'mix.str': [], + + # Group bcc + 'bcc': [], + + # Scope project + 'project': [ + + OptionModel( + name='src', + cls='PathOption', + examples=[ + '', '.', 'src', '../src', + '/home/jondy/project/src1', + 'my projects/project/src4' + 'C:\\test\\src2', 'C:\\工程\\src3', + ] + ), + + OptionModel( + name='scripts', + cls='FileOption', + many=True, + restricts=[ + 'ext should be .py, .pyw', + 'relative to src, or absolute path', + ], + examples=['foo.py', 'test/run.py'] + ), + + OptionModel( + name='modules', + cls='FileOption', + many=True, + ), + + OptionModel( + name='packages', + cls='PkgPathOption', + many=True, + ), + + # Only match name, no path separator + OptionModel( + name='excludes', + cls='PatternOption', + many=True, + examples=['__pycache__', 'test*'] + ), + + OptionModel( + name='recursive', + cls='BoolOption', + value=0, + ), + ], + + 'rft_option': [ + OptionModel( + name='rft_remove_assert', + cls='BoolOption', + value=0, + ), + OptionModel( + name='rft_remove_docstr', + cls='BoolOption', + value=0, + ), + OptionModel( + name='rft_builtin', + cls='BoolOption', + value=0, + ), + OptionModel( + name='rft_argument', + cls='RftArgEnum', + value='1', + ), + OptionModel( + name='on_unknown_attr', + cls='RftAttrEnum', + value='log', + ), + OptionModel( + name='rft_auto_export', + cls='BoolOption', + value=0, + ), + OptionModel( + name='extra_builtins', + cls='NameOption', + many=True, + ), + OptionModel( + name='var_type_table', + cls='DictOption', + ), + OptionModel( + name='extra_type_info', + cls='DictOption', + ), + OptionModel( + name='rft_exclude_names', + cls='RftNamePattern', + many=True, + ), + OptionModel( + name='rft_exclude_args', + cls='RftNamePattern', + many=True, + ), + OptionModel( + name='rft_str_keywords', + cls='DictOption', + ), + ], + 'rft_filter': [ + OptionModel( + name='obf_attribute', + cls='BoolOption', + ), + OptionModel( + name='obf_string', + cls='BoolOption', + ), + OptionModel( + name='obf_include_strings', + cls='PatternOption', + many=True, + ), + OptionModel( + name='obf_attr_filters', + cls='RftAttrFilter', + ), + OptionModel( + name='rft_import', + cls='BoolOption', + ), + OptionModel( + name='rft_ximport', + cls='BoolOption', + ), + OptionModel( + name='wildcard_import_table', + cls='DictOption', + ), + ], +} + + +GROUPS = { + 'filter': ('assert.import', 'assert.call', 'mix.str'), + 'bcc': ('bcc', + 'linux.x86_64.bcc', 'linux.x86.bcc', + 'linux.aarch64.bcc', 'linux.armv7.bcc', + 'darwin.x86_64.bcc', 'darwin.aarch64.bcc', + 'windows.x86_64.bcc', 'windows.x86.bcc', + 'android.x86_64.bcc', 'android.x86.bcc', + 'android.aarch64.bcc', 'android.armv7.bcc', + 'alpine.x86_64.bcc', 'alpine.aarch64.bcc', + 'freebsd.x86_64.bcc'), +} + +BUSINESS_MODELS = { + + 'Project': [ + Item('scripts', cls='Python.Script', many=True), + Item('modules', cls='Python.Module', many=True), + Item('packages', cls='Python.Package', many=True), + ], + + 'StdScript': [ + Item('script', cls='Python.Script', require=True), + Item('pyarmor_runtime', cls='Python.Extension', require=True), + ], + + 'MiniScript': [ + Item('script', cls='Python.Script', require=True), + Item('pyarmor_mini', cls='Python.Extension', require=True), + ], + + 'EccScript': [ + Item('script', cls='Python.Script', require=True), + Item('pyarmor_ecc', cls='Python.Extension', require=True), + ], + + 'VmcScript': [ + Item('script', cls='Python.Script', require=True), + Item('pyarmor_ecc', cls='Python.Extension', require=True), + ], + + 'RftScript': [ + Item('script', cls='Python.Script', require=True), + ] +} diff --git a/src/cli/shell.py b/src/cli/shell.py index 5fb99ec5..a89f76ea 100644 --- a/src/cli/shell.py +++ b/src/cli/shell.py @@ -3,12 +3,12 @@ # ############################################################# # # -# Copyright @ 2023 - Dashingsoft corp. # +# Copyright @ 2024 - Dashingsoft corp. # # All rights reserved. # # # # Pyarmor # # # -# Version: 8.0.1 - # +# Version: 9.1.0 - # # # ############################################################# # @@ -17,64 +17,462 @@ # # @Author: Jondy Zhao (pyarmor@163.com) # -# @Create Date: Thu Jan 12 10:27:05 CST 2023 +# @Create Date: Fri Nov 15 10:52:00 CST 2024 # import configparser import cmd +import shlex +from .model import SECTIONS, GROUPS -class PyarmorShell(cmd.Cmd): - intro = 'Welcome to the Pyarmor shell. Type help or ? to list commands.\n' - prompt = '(pyarmor) ' +class CfgEntity: + """Store option to cfg file""" - def __init__(self, ctx): - super().__init__() - self.ctx = ctx - self._reset() + def __init__(self, cfgfiles, encoding=None): + self._cfgfiles = cfgfiles + self._reader = None + self._writer = None + self._index = -1 + self._encoding = encoding - def _reset(self): - ctx = self.ctx + def _load_cfg(self, filenames): cfg = configparser.ConfigParser( empty_lines_in_values=False, interpolation=configparser.ExtendedInterpolation(), ) - cfg.read([ctx.default_config, ctx.global_config, ctx.local_config]) - self._cfg = cfg + cfg.read(filenames, encoding=self._encoding) + return cfg + + @property + def cfgfile(self): + return self._cfgfiles[self._index] + + @property + def reader(self): + if self._reader is None: + self._reader = self._load_cfg(self._cfgfiles) + return self._reader + + @property + def writer(self): + if self._writer is None: + self._writer = self._load_cfg(self.cfgfile) + return self._writer + + def store(self, sect, opt, value): + for cfg in (self.writer, self.reader): + if not cfg.has_section(sect): + cfg.add_section(sect) + cfg.set(sect, opt, value) + + def fetch(self, sect, opt): + cfg = self.reader + if cfg.has_section(sect): + return cfg[sect].get(opt, None) + + def remove(self, sect, opt=None): + for cfg in (self.writer, self.reader): + if cfg.has_section(sect): + if opt is None: + cfg.remove_section(sect) + else: + cfg.remove_option(sect, opt) + + def reset(self, index): + if self._index == index: + return + self._index = index + self._reader = self._writer = None + + def save(self): + with open(self.cfgfile, 'w') as f: + self.writer.write(f) + + +class Domain: + + def __init__(self, name, default='pyarmor'): + self.name = name + self._dirty = False + + self._sections = [] + self._groups = [] + + self._default = Section(default, parent=self) + self._cfgentity = None + self._children = None + + @property + def qualname(self): + return self.name + + @property + def title(self): + return '(%s) ' % self.name + + @property + def dirty(self): + return self._dirty + + @dirty.setter + def dirty(self, value): + self._dirty = value + + @property + def groups(self): + for x in self._groups: + yield x + + @property + def sections(self): + for x in self._sections: + yield x + + @property + def options(self): + if self._default: + for x in self._default.options: + yield x + + @property + def children(self): + if self._children is None: + items = {x.name: x for x in self.groups} + items.update({x.name: x for x in self.sections}) + self._children = items + return self._children + + @property + def cfgentity(self): + return self._cfgentity + + def link(self, cfgentity): + self._cfgentity = cfgentity + + def save(self, force=False): + cfgentity = self._cfgentity + if (self.dirty or force) and cfgentity: + cfgentity.save() + self._dirty = False + + def add_section(self, name): + if self._default and self._default.name == name: + return + self._sections.append(Section(name, parent=self)) + + def add_group(self, name, sections): + group = Group(name, parent=self) + group.add_sections(sections) + self._groups.append(group) + + +class Section: + + def __init__(self, name, parent=None): + self.parent = parent + self.name = name + self.models = SECTIONS.get(name, []) + self._options = None + self._children = {} + + @property + def qualname(self): + parent = self.parent + name = self.name + return ':'.join([parent.qualname, name]) + + @property + def title(self): + return '({0})[{1}] '.format(*self.qualname.split(':', 1)) + + @property + def options(self): + if self._options is None: + self._options = [x.factory(self) for x in self.models] + + for x in self._options: + yield x + + @property + def children(self): + return self._children - def _reset_prompt(self): - prompts = ['(pyarmor) '] - self.prompt = '\n'.join(prompts) + @property + def cfgentity(self): + return self.parent.cfgentity - def do_exit(self, arg): - 'Finish config and exit' - print('Thank you for using Pyarmor') + def store(self, opt, value): + self.cfgentity.store(self.name, opt, value) + + def fetch(self, opt): + return self.cfgentity.fetch(self.name, opt) + + def remove(self, opt): + self.cfgentity.remove(self.name, opt) + + +class Group(Section): + + def __init__(self, name, parent=None, sections=None): + super().__init__(name, parent=parent) + self._groups = [] + self._sections = [] + + @property + def group(self): + for x in self._group: + yield x + + @property + def sections(self): + for x in self._sections: + yield x + + @property + def children(self): + if self._children is None: + items = {x.name: x for x in self.groups} + items.update({x.name: x for x in self.sections}) + self._children = items + return self._children + + def add_group(self, name, sections): + group = Group(name, parent=self) + group.add_sections(sections) + self._group.append(group) + + def add_sections(self, sections): + self._sections.extend([ + Section(x, parent=self) for x in sections + ]) + + +class PyarmorShell(cmd.Cmd): + + intro = 'Type help or ? to list commands.\n' + col = 60 + + def __init__(self, ctx, domain='local'): + super().__init__() + self.ctx = ctx + self._entity = CfgEntity([ + ctx.default_config, + ctx.global_config, + ctx.local_config + ]) + self._domains = self._init_domains() + self._domain = self._section = self._domains[domain] + + def _init_domains(self): + gs = Domain('global') + ls = Domain('local') + ps = Domain('project', default='project') + + gs.link(self._entity) + ls.link(self._entity) + ps.link(self._entity) + + sections = 'finder', 'builder', 'runtime', 'pack' + [(gs.add_section(x), ls.add_section(x)) for x in sections] + + for domain in (gs, ls): + for name in ('filter', 'bcc'): + domain.add_group(name, GROUPS[name]) + + ps.add_section('rft_option') + + return {'global': gs, 'local': ls, 'project': ps} + + @property + def section(self): + return self._section + + @property + def prompt(self): + return self.section.title + + def get_sections(self, name=''): + obj = self.section + sects = [x for x in getattr(obj, 'groups', [])] + sects.extend([x for x in getattr(obj, 'sections', [])]) + return (sects if name in ('-a', '*', '') else + [x for x in sects if x.name.startswith(name)]) + + def get_options(self, name=''): + options = [x for x in self.section.options] + return (options if name in ('-a', '*', '') else + [x for x in options if x.name.startswith(name)]) + + def printf(self, *args): + print(*args) + + def find(self, opt): + for item in self.section.options: + if item.name == opt: + return item + + def save(self): + self._domain.save() + + def do_exit(self, arg=None): + """Finish config and exit""" + self.save() + self.printf('') + self.printf('Thank you for using Pyarmor') return True do_EOF = do_q = do_exit def do_use(self, arg): - 'Select config file' + """Switch domain: global, local, project""" + names = list(self._domains.keys()) + if arg not in names: + self.printf('Invalid name: ', arg) + self.printf('Please type one of', str(names)) + elif self._domain.name == arg: + pass + else: + self._domain.save() + if arg == 'global': + self._entity.reset(index=1) + elif self._domain.name == 'global': + self._entity.reset(index=-1) + self._domain = self._domains[arg] + self._section = self._domain def do_ls(self, arg): - '''List all the available items in current scope''' + """List all the available items in current domain""" + sections = self.get_sections(arg) + if sections: + self.printf('Sections:') + self.columnize([x.name for x in sections], self.col) + self.printf('') + + options = self.get_options(arg) + if options: + self.printf('Options:') + self.columnize([x.name for x in options], self.col) + self.printf('') def do_cd(self, arg): - '''Change scope''' + """Switch to section, .. to parent, blank to top""" + if arg == '..': + if self._section is not self._domain: + self._section = self._section.parent + elif arg: + item = self.section.children.get(arg) + if item: + self._section = item + else: + self.printf('No item', repr(arg)) + else: + self._section = self._domain - def do_rm(self, arg): - '''Remove item in the scope''' + def do_get(self, arg): + """Show option value""" + options = self.get_options(arg) + if not options: + self.printf('No found any', repr(arg)) + return + for opt in options: + value = opt.value + if value is not None: + self.printf('%-20s= %s' % (opt.name, value)) + + def do_info(self, arg): + """List sections, options, and all the values""" + if arg == '': + self.do_ls('') + self.do_get('') + return + item = self.section.children.get(arg) + if item: + self.do_cd(arg) + self.do_ls('') + self.do_get('') + self.do_cd('..') + else: + item = self.find(arg) + if item: + self.printf(item.usage()) + self.do_get(arg) + else: + self.printf('No found', arg) def do_set(self, arg): - 'Change option value' + """Set option value""" + if arg.find(' ') == -1: + self.printf('Missing value') + return + name, value = arg.split(None, 1) + item = self.find(name) + if item: + c = value[:1] + if c in ('"', "'"): + value = value.strip(c) + try: + item.value = value + self._domain.dirty = True + except ValueError as e: + self.printf(str(e)) + else: + self.printf('No found', name) - def do_show(self, arg): - 'Show option value' + def do_reset(self, arg): + """Reset option in the domain""" + if arg in ('', '*'): + self.printf('Please specify one option') + return + item = self.find(arg) + if item: + item.reset() + self._domain.dirty = True + else: + self.printf('No found', arg) + def do_push(self, arg, silent=False): + """Append new value to option""" + if arg.find(' ') == -1: + self.printf('Missing value') + return + name, value = arg.split(None, 1) + item = self.find(name) + if item: + for x in shlex.split(value): + try: + item.push(x) + except ValueError as e: + self.printf(str(e)) + break + else: + self._domain.dirty = True + self.do_get(name) + else: + self.printf('No found', name) -def parse(arg): - 'Convert a series of zero or more numbers to an argument tuple' - return tuple(map(int, arg.split())) + def do_pop(self, arg): + """Remove one choice from option""" + if arg.find(' ') == -1: + self.printf('Missing value') + return + name, value = arg.split(None, 1) + item = self.find(name) + if item: + for x in shlex.split(value): + item.pop(x) + self._domain.dirty = True + self.do_get(name) + else: + self.printf('No found', name) if __name__ == '__main__': - PyarmorShell().cmdloop() + from os.path import join as joinpath, abspath, expanduser + + from .context import Context + + home = joinpath('~', '.pyarmor') + home = abspath(expanduser(home)) + + ctx = Context(home) + PyarmorShell(ctx).cmdloop() From d9867e17a9aaf9555c9080e96eeacab86c8b8eda Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 2 Jan 2025 22:08:33 +0800 Subject: [PATCH 130/337] Add project.py --- src/cli/project.py | 781 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 781 insertions(+) create mode 100755 src/cli/project.py diff --git a/src/cli/project.py b/src/cli/project.py new file mode 100755 index 00000000..25d30dd4 --- /dev/null +++ b/src/cli/project.py @@ -0,0 +1,781 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# +############################################################# +# # +# Copyright @ 2024 Dashingsoft corp. # +# All rights reserved. # +# # +# pyarmor # +# # +# Version: 9.1.0 - # +# # +############################################################# +# +# +# @File: pyarmor/cli/project.py +# +# @Author: Jondy Zhao(pyarmor@163.com) +# +# @Create Date: Tue Nov 12 16:38:51 CST 2024 +# +# @Description: +# +# - Define project object for Pyarmor 9. +# - Define project commands: init, build +# - Define 3 targets: std, mini, plain + +"""Manage projects + +Config View +----------- + +[project] +global_excludes = .* __pycache__ +project_includes = *.py *.pyw +package_includes = *.py *.pyw +recursive = 0 + +name = str +src = absolute path + +modules = pattern pattern ... +packages = pattern path@name @section ... +excludes = patterh patterh ... + +rpath = path path + +[project.name] +name = +path = + +modules = +packages = 0 or 1 +excludes = + +rpath = + +Examples +-------- + +1. Create a project in current path + + $ pyarmor init + +2. Obfuscate all the scripts in the project + + $ pyarmor build --std + $ pyarmor build --mini + $ pyarmor build --plain + +3. Config project and print project information + + $ pyarmor env -p + $ pyarmor env -p set remove_docstring 1 + +""" +import ast +import glob +import logging +import os +import tokenize + +from fnmatch import fnmatch +from collections import namedtuple +from os.path import ( + abspath, basename, exists, isabs, join as joinpath, + normpath, relpath, splitext +) +from string import Template +from textwrap import dedent + + +logger = logging.getLogger('cli.project') + + +GRAPHVIZ_INDENT = ' ' + +############################################################ +# +# Project File View +# +############################################################ + +GLOBAL_EXCLS = '.*', '__pycache__' +GLOBAL_INCLS = '*.py', '*.pyw' + +ProjectItem = namedtuple( + 'ProjectItem', + ('name', 'src', 'scripts', 'modules', 'packages', + 'excludes', 'recursive'), + defaults=['', '', [], [], [], None, False] +) + + +def scan_path(path, includes=None, excludes=[], **options): + files, dirs = [], [] + xlist = includes if includes else GLOBAL_INCLS + with os.scandir(path) as itdir: + for et in itdir: + if any([fnmatch(et.name, x) for x in excludes]): + continue + if et.is_dir(follow_symlinks=False): + dirs.append(et.name) + elif (et.is_file(follow_symlinks=False) and + any([fnmatch(et.name, x) for x in xlist])): + files.append(et.name) + return files, dirs + + +def search_item(root, pattern, excludes, recursive=0): + if not pattern: + return [] + + sep = os.sep if pattern.endswith(os.sep) else '' + result = [] + + pt = pattern if isabs(pattern) else joinpath(root, pattern) + for item in glob.glob(pt, recursive=recursive): + name = basename(item.strip(sep)) + if excludes and any([fnmatch(name, x) for x in excludes]): + continue + result.append(item) + return [normpath(x) for x in result] + + +############################################################ +# +# Concepts +# +############################################################ + +class Module: + """Module concept""" + + def __init__(self, path, name=None, parent=None): + self.parent = parent + self._path = path + self._name = name if name else splitext(basename(path))[0] + + self._co = None + self._tree = None + self._type = None + + @property + def name(self): + return '' if self._name == '__init__' else self._name + + @property + def path(self): + return self._path + + @property + def mtype(self): + return self._type + + @property + def mtree(self): + return self._tree + + @property + def qualname(self): + if isinstance(self.parent, (Project, type(None))): + return self._name + prefix = self.parent.qualname + ('.' if self.name else '') + return prefix + self.name + + @property + def project(self): + """Return project this module belong to""" + return (self.parent if isinstance(self.parent, Project) + else self.parent.project) + + @property + def abspath(self): + return (self._path if isabs(self._path) else + joinpath(self.parent.abspath, self._path)) + + def compile_file(self, force=False): + if self._co is not None and not force: + return + + self.parse_file(force=force) + + logger.info('compile %s ...', self.qualname) + self._co = compile(self._tree, self.abspath, 'exec') + logger.info('compile %s end', self.qualname) + + def parse_file(self, force=False): + if self._tree is not None and not force: + return + + filename = self.abspath + with open(filename, 'rb') as f: + encoding, _ = tokenize.detect_encoding(f.readline) + + with open(filename, 'r', encoding=encoding) as f: + logger.info('parse %s ...', self.qualname) + self._tree = ast.parse(f.read(), filename, 'exec') + logger.info('parse %s end', self.qualname) + + def _as_dot(self): + return self.name + + +class Script(Module): + """Script concept""" + + @property + def rpaths(self): + """Extra Python paths for importing module + + If one script has any extra pypath, it need map imported + module name to project module name + + For example, in the script `import abc`, maybe it uses module + `pkg.abc` in this project + + This is only used by RFT mode, otherwise it doesn't know where + to find module `abc` + + This property is used to generate internal `_mapped_modules` + """ + pass + + +class Package(Module): + """Package concept""" + + def __init__(self, path, name=None, parent=None): + super().__init__(path, name=name, parent=parent) + + self._modules = None + self._packages = None + + def load(self): + excludes = GLOBAL_EXCLS + files, dirs = scan_path(self.abspath, excludes=excludes) + self._modules = [Module(x, parent=self) for x in files] + self._packages = [Package(x, parent=self) for x in dirs] + + @property + def modules(self): + """Each package has many modules + + There is one special module `__init__` for Package + """ + if self._modules is None: + self.load() + + for x in self._modules: + yield x + + @property + def packages(self): + """Each package has many sub-packages""" + if self._packages is None: + self.load() + + for x in self._packages: + yield x + + def iter_module(self): + if self._modules is None or self._packages is None: + self.load() + + for x in self._modules: + yield x + + for pkg in self._packages: + for x in pkg.iter_module(): + yield x + + def _as_dot(self, n=0): + modules = [x._as_dot() for x in self.modules] + packages = [x._as_dot(n+1) for x in self.packages] + sep = '\n' + GRAPHVIZ_INDENT + source = Template(dedent("""\ + subgraph cluster_$cid { + label="$name"; + $modules + $packages + }""")).substitute( + cid=id(self), + name=self.name, + modules=sep.join(modules), + packages=sep.join(packages), + ) + return ( + ('\n' + GRAPHVIZ_INDENT * n).join(source.splitlines()) + if n else source + ) + + +class Namespace: + """Namespace concept""" + + @property + def name(self): + pass + + @property + def components(self): + """Each component has 3 items: path, modules, children + + Each child is Namespace or Package + """ + return [] + + +class Project: + """Project conpect + + Project is compose of Python elements and obfuscation settings + + Each project has 4 components: + + - Script + - Module + - Package + - Namespace + + Each component has one unique name in this project except Script + + It may has alias which also can't be duplicated with other names + + Refer + ----- + + https://docs.python.org/3.13/reference/import.html#namespace-packages + + """ + + def __init__(self, ctx): + self.ctx = ctx + self.src = '' + + self._scripts = [] + self._modules = [] + self._packages = [] + self._namespaces = [] + + self._rft_options = None + self._rft_filters = None + self._rft_rulers = None + + self._rmodules = None + self._builtins = None + + # 在重构过程中记录类型未知,但是需要更改其属性的变量 + self.unknown_vars = [] + + # 在重构过程中记录类型已知,但是属性没有在类型中定义 + self.unknown_attrs = {} + + # 在重构过程中记录使用 **kwarg 方式调用的函数列表 + self.unknown_calls = [] + + @property + def abspath(self): + return abspath(self.src) + + @property + def scripts(self): + """Project entry points + + One project may has many entry points + + Script can't be imported by other components + """ + for x in self._scripts: + yield x + + @property + def modules(self): + for x in self._modules: + yield x + + @property + def packages(self): + """Only top packages""" + for x in self._packages: + yield x + + @property + def namespaces(self): + """Only top namespace""" + for x in self._namespaces: + yield x + + @property + def rft_options(self): + """Refactor options: + + - rft_remove_assert: bool + + If 1, remove assert node + + - rft_remove_docstr: bool + + If 1, remove all docstring + + - rft_builtin: bool + + 0, do not touch any builtin names + 1, as build target, maybe std, mini or plain + + - rft_import: bool + + always 1 + + - rft_ximport: bool + + always 0, do not touch node "from..import *" + + - rft_argument: enum('no', 'pos', '!kw', 'all') + + 0: "no", no reform any argument node + 1: "pos", reform posonly arguments + 2: '!kw', no reform keyword only arguments + 3: "all", reform all arguments + + Note that if function is exported, no arguments reformed + + - obf_attribute: enum(no, yes, all) + + Reform attribute node to setattr() or getattr() + + 0, do not reform attribute to setattr or getattr + 1, reform attribute node by rft_attribute_filters + 2, reform all attribute node + + - obf_string: enum(no, yes, all) + + Reform string constant to security mode + + It only works for std/mini target + + It's always 0 for plain target + + 0, no reform string + 1, reform string by rft_string_filters + 2, reform all string + + - rft_auto_export: bool + + Auto export all names in module.__all__ + + - rft_exclude_names: list + + Move it from rft_filter + + - rft_exclude_args: list + + Move it from rft_filter + + - rft_str_keywords: list (not implemented) + + Rename string constant or key in dict constant + + When call function, solve argument not found issue + + - var_type_table: dict + + Specify variable type + + - extra_type_info: dict + + Specify extra attribute for module or type + + - wildcard_import_table: dict (unused now) + + When building target, it need import module to get name + for wildcard import, sometimes it may failed + + In order to avoid importing module in build time, users + can provide all names in wildcard imported module + + - extra_builtins: list + + By default, builtin names is got from builtin module + + User can append extra builtin names + + - on_unknown_attr: enum(ask, log, yes, no, err) + + When don't know how to refactor attribute + + ask: query user interactively + log: no touch attr but log it + yes: rename attr + no: do thing + err: raise error + """ + if self._rft_options is None: + cfg = self.ctx.cfg + sect = 'rft_option' + if cfg.has_section(sect): + self._rft_options = dict(cfg.items(sect)) + else: + self._rft_options = {} + return self._rft_options + + def opt(self, name): + return self.rft_options.get(name) + + @property + def rft_exclude_names(self): + """Exclude module, class, function + + All names in this scope aren't renamed. For example, + + Exclude module, all classes and functions aren't renamed + Exclude class, all class attributes aren't renamed + + Each ruler is one chained names + Each ruler must start with package or module name + It supports pattern match as fnmatchcase + Pattern only match one level + + It's also used to export names manually + """ + value = self.opt('rft_exclude_names') + if value: + for x in value.splitlines(): + yield x + + @property + def rft_exclude_args(self): + """No refactor arguments of the functions in this list""" + value = self.opt('rft_exclude_args') + if value: + for x in value.splitlines(): + yield x + + @property + def rft_filters(self): + if self._rft_filters is None: + cfg = self.ctx.cfg + sect = 'rft_filter' + if cfg.has_section(sect): + self._rft_filters = dict(cfg.items(sect)) + else: + self._rft_filters = {} + return self._rft_filters + + @property + def obf_include_strings(self): + """A list of re pattern based on obf_string + + All matched string in ast.Tree will be transformed + """ + value = self.rft_filters.get('obf_include_strings', '') + for x in value.splitlines(): + yield x + + @property + def obf_attr_filters(self): + """A list of re pattern based on obf_attribute + + All matched ast.Attribute will be transformed to call + setattr() or getattr() to hide attribute name + """ + value = self.rft_filters.get('obf_attr_filters', '') + for x in value.splitlines(): + yield x + + @property + def rft_rulers(self): + if self._rft_rulers is None: + cfg = self.ctx.cfg + sect = 'rft_ruler' + if cfg.has_section(sect): + self._rft_rulers = dict(cfg.items(sect)) + else: + self._rft_rulers = {} + return self._rft_rulers + + @property + def rft_attr_rulers(self): + """Refactor attribute rulers, for special attribute node + + If can't decide variable type, use ruler for chains + + For example, "x.a", if "x" of type is unknown + + Use ruler "x.a" to rename attribute "a" + + Use ruler "!x.a" to keep attribute "a" + """ + value = self.rft_rulers.get('rft_attr_rulers', '') + for x in value.splitlines(): + yield x + + @property + def rft_arg_rulers(self): + """Refactor ruler, for arg name in Function/Call node + + For example, in the call statement + + kwargs = { 'msg': 'hello' } + foo(**kwargs) + + This kind of rule could be used to rename string `msg` + """ + value = self.rft_rulers.get('rft_arg_rulers', '') + for x in value.splitlines(): + yield x + + @property + def builtins(self): + if self._builtins is None: + import builtins + self._builtins = set(dir(builtins)) + return self._builtins + + def std_options(self): + """Obfuscation options only for std target + + - std_assert_import + - std_assert_call + - std_restrict_module + - std_expired_date + - std_bind_devices + + Got from command line, not in config file + """ + pass + + def get_module(self, qualname): + """Get module in the project by unique qualname + It equals one dict: map_qualname_to_module + """ + if self._rmodules is None: + self._rmodules = { + x.qualname: x for x in self.iter_module() + } + return self._rmodules.get(qualname) + + def iter_module(self): + """Iterate all modules in this project""" + for x in self._modules: + yield x + + for child in self._packages + self._namespaces: + for x in child.iter_module(): + yield x + + def relsrc(self, path): + return relpath(path, self.src) + + def load(self, data): + """Init project object with dict + + It equals: + + 1. map init data to ProjectItem + 2. map ProjectItem to project files + """ + dp = joinpath(self.ctx.local_path, 'project') + os.makedirs(dp, exist_ok=True) + + def vlist(name): + return [x.strip().replace('%20%', ' ') + for x in data.get(name, '').split()] + + src = self.src = data['src'] + name = data.get('name') + excludes = vlist('excludes') + list(GLOBAL_EXCLS) + + scripts = [] + for pat in vlist('scripts'): + scripts.extend(search_item(src, pat, excludes)) + self._scripts.extend([ + Script(self.relsrc(x), parent=self) for x in scripts + ]) + + modules = [] + for pat in vlist('modules'): + modules.extend(search_item(src, pat, excludes)) + + packages = vlist('packages') + if packages: + for item in packages: + # 3 forms: path, path@name, @sect + i = item.find('@') + if i == 0: + logger.info('package at section %s', item) + continue + if i == -1: + path, pkgname = item, basename(item) + else: + path, pkgname = item.split('@') + if not isabs(path): + path = joinpath(src, path) + obj = Package(path, name=pkgname, parent=self) + self._packages.append(obj) + + elif not modules and not scripts: + pkginit = joinpath(src, '__init__.py') + if exists(pkginit): + pkgname = name if name else basename(src) + obj = Package(src, name=pkgname, parent=self) + self._packages.append(obj) + else: + files, dirs = scan_path(src, excludes=excludes) + modules.extend([joinpath(src, x) for x in files]) + self._packages.extend([ + Package(x, parent=self) for x in dirs + ]) + + if scripts and modules: + for x in set(scripts) & set(modules): + logger.debug('duplicated %s', self.relsrc(x)) + modules.remove(x) + self._modules.extend([ + Module(self.relsrc(x), parent=self) for x in modules + ]) + + logger.info('load %d scripts', len(self._scripts)) + logger.info('load %d modules', len(self._modules)) + logger.info('load %d packages', len(self._packages)) + + def log_unknown_var(self, module, var): + key = '%s:%s' % (module, var) + if key not in self.unknown_vars: + self.unknown_vars.append(key) + + def log_unknown_call(self, ftype): + if isinstance(ftype, str): + func = '{%s}' % ftype + else: + func = ':'.join(ftype.module, '.'.join(ftype.scopes)) + + if func not in self.unknown_calls: + self.unknown_calls.append(func) + + def build(self, target='std'): + """Build project to generate obfuscated scripts""" + pass + + def _as_dot(self): + """Map project to dot graph""" + modules = [x._as_dot() for x in self.modules] + packages = [x._as_dot() for x in self.packages] + sep = '\n' + GRAPHVIZ_INDENT * 2 + return Template(dedent("""\ + graph { + layout=osage + subgraph cluster_0 { + label="Project Structure"; + $modules + $packages + } + }""")).substitute( + modules=sep.join(modules), + packages=sep.join('\n'.join(packages).splitlines()) + ) + + +if __name__ == '__main__': + pass From 98484abc9fc328a43af9ab932f3200296dda6699 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 2 Jan 2025 22:20:28 +0800 Subject: [PATCH 131/337] Fix bugs --- src/cli/__main__.py | 9 +++++---- src/cli/command.py | 19 +++++++------------ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 8608ee52..75db1745 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -370,7 +370,7 @@ def cmd_man(ctx, args): check_call([sys.executable, '-m', 'pyarmor.man.shell']) -def main_parser(cmd): +def main_parser(cmd=None): parser = argparse.ArgumentParser( prog='pyarmor', fromfile_prefix_chars='@', @@ -401,9 +401,10 @@ def main_parser(cmd): gen_parser(subparsers) reg_parser(subparsers) - cmd.env_parser(subparsers) - cmd.init_parser(subparsers) - cmd.build_parser(subparsers) + if cmd: + cmd.env_parser(subparsers) + cmd.init_parser(subparsers) + cmd.build_parser(subparsers) cfg_parser(subparsers) man_parser(subparsers) diff --git a/src/cli/command.py b/src/cli/command.py index 554b3a32..a1764d27 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -33,9 +33,6 @@ class Commander: - def __init__(self, ctx=None): - self.ctx = ctx - def init_parser(self, subparsers): parser = subparsers.add_parser( 'init', @@ -223,17 +220,16 @@ def main_parser(self): return parser - def run(self, argv): + def run(self, ctx, argv): parser = self.main_parser() args = parser.parse_args(argv) if hasattr(args, 'func'): - args.func(args) + args.func(ctx, args) else: parser.print_help() - def cmd_init(self, args): + def cmd_init(self, ctx, args): logger.debug('init %s', args) - ctx = self.ctx sep = ',' cfgsep = ' ' @@ -318,7 +314,7 @@ def format_path(plist, raw=False): cfg.write(f) logger.info('project saved') - def cmd_build(self, args): + def cmd_build(self, ctx, args): """Obfuscate all scripts, modules and packages in project Show project information: @@ -344,7 +340,6 @@ def cmd_build(self, args): """ logger.debug('build %s', args) - ctx = self.ctx cfg = configparser.ConfigParser( empty_lines_in_values=False, interpolation=configparser.ExtendedInterpolation(), @@ -390,7 +385,7 @@ def _build(self, project, target, output): analysis_project(project) build_project(project, target, output) - def cmd_env(self, args): + def cmd_env(self, ctx, args): """Check and change pyarmor settings Enter interactive mode: @@ -420,7 +415,7 @@ def cmd_env(self, args): """ logger.debug('env %s', args) logger.info('enter domain: %s', args.domain) - shell = PyarmorShell(self.ctx, domain=args.domain) + shell = PyarmorShell(ctx, domain=args.domain) if args.exprs: exprs = args.exprs @@ -476,7 +471,7 @@ def test_main(args, log=False): ctx = Context(home) if log: logging.config.fileConfig(ctx.default_config) - Commander(ctx).run(args) + Commander().run(ctx, args) if __name__ == '__main__': From 056c0877da3fd541c4f4a1eec1d1640b4252bc75 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 3 Jan 2025 00:55:11 +0800 Subject: [PATCH 132/337] Refine code --- src/cli/command.py | 29 ++++++++++++++++++++++------- src/cli/project.py | 13 +++++-------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index a1764d27..eb8e2b30 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -26,7 +26,7 @@ from os import makedirs from os.path import abspath, exists, join as joinpath, relpath -from . import logger +from . import logger, CliError from .project import Project from .shell import PyarmorShell @@ -258,7 +258,7 @@ def cmd_init(self, ctx, args): if not sect.get('src'): sect['src'] = src elif src != sect['src']: - raise RuntimeError( + raise CliError( 'project has another src, ' 'use option "-C" to fix this issue' ) @@ -269,7 +269,7 @@ def cmd_init(self, ctx, args): sect['src'] = src if not exists(src): - raise RuntimeError('no found src "%s"' % src) + raise CliError('no found src "%s"' % src) def format_path(plist, raw=False): r = [] @@ -348,7 +348,7 @@ def cmd_build(self, ctx, args): sectname = 'project' if not cfg.has_section(sectname): - raise RuntimeError('no project information') + raise CliError('no project information') cfgdata = dict(cfg.items(sectname)) logger.debug('project: %s', cfgdata) @@ -381,9 +381,24 @@ def cmd_build(self, ctx, args): logger.info('build target %s end', args.target) def _build(self, project, target, output): - from .rftbuild import analysis_project, build_project - analysis_project(project) - build_project(project, target, output) + try: + from .rftbuild import rft_project + except Exception as e: + logger.debug('no refactor because of %s', str(e)) + else: + rft_project(project, target, output) + + if target == '--mini': + self._build_mini(project, output) + # elif target == '--vmc': + # self._build_vmc(project, output) + # elif target == '--ecc': + # self._build_ecc(project, output) + # elif target == '--std': + # self._build_std(project, output) + + def _build_mini(self, project, output): + pass def cmd_env(self, ctx, args): """Check and change pyarmor settings diff --git a/src/cli/project.py b/src/cli/project.py index 25d30dd4..cee3b05f 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -745,14 +745,11 @@ def log_unknown_var(self, module, var): if key not in self.unknown_vars: self.unknown_vars.append(key) - def log_unknown_call(self, ftype): - if isinstance(ftype, str): - func = '{%s}' % ftype - else: - func = ':'.join(ftype.module, '.'.join(ftype.scopes)) - - if func not in self.unknown_calls: - self.unknown_calls.append(func) + def log_unknown_call(self, func): + name = ('{%s}' % func if isinstance(func, str) else + ':'.join([func.module, '.'.join(func.scopes)])) + if name not in self.unknown_calls: + self.unknown_calls.append(name) def build(self, target='std'): """Build project to generate obfuscated scripts""" From 5c95b16f8a959a14f29d9d2e1f28f06bcf0ff029 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 3 Jan 2025 05:20:18 +0800 Subject: [PATCH 133/337] Refine code --- src/cli/command.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index eb8e2b30..691c1b23 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -381,24 +381,9 @@ def cmd_build(self, ctx, args): logger.info('build target %s end', args.target) def _build(self, project, target, output): - try: - from .rftbuild import rft_project - except Exception as e: - logger.debug('no refactor because of %s', str(e)) - else: - rft_project(project, target, output) - - if target == '--mini': - self._build_mini(project, output) - # elif target == '--vmc': - # self._build_vmc(project, output) - # elif target == '--ecc': - # self._build_ecc(project, output) - # elif target == '--std': - # self._build_std(project, output) - - def _build_mini(self, project, output): - pass + from .core import Pytransform3 + args = [self.ctx, target, project, output] + Pytransform3.pre_build(args) def cmd_env(self, ctx, args): """Check and change pyarmor settings @@ -479,6 +464,7 @@ def test_main(args, log=False): from os.path import expanduser from .context import Context + from .rftbuild import rft_build_project home = joinpath('~', '.pyarmor') home = abspath(expanduser(home)) @@ -486,7 +472,9 @@ def test_main(args, log=False): ctx = Context(home) if log: logging.config.fileConfig(ctx.default_config) - Commander().run(ctx, args) + cmd = Commander() + cmd._build = rft_build_project + cmd.run(ctx, args) if __name__ == '__main__': From 9413cac46da351652743ea3d6d0839d25f95fb2d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 3 Jan 2025 21:25:24 +0800 Subject: [PATCH 134/337] Refine code --- src/cli/command.py | 2 +- src/cli/project.py | 32 ++++++++++++++++++-------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index 691c1b23..d8fa3d6f 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -459,7 +459,7 @@ def cmd_env(self, ctx, args): shell.cmdloop() -def test_main(args, log=False): +def test_main(args, target='rft', log=False): import logging.config from os.path import expanduser diff --git a/src/cli/project.py b/src/cli/project.py index cee3b05f..1d022c93 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -64,14 +64,13 @@ 2. Obfuscate all the scripts in the project - $ pyarmor build --std + $ pyarmor build --rft $ pyarmor build --mini - $ pyarmor build --plain 3. Config project and print project information $ pyarmor env -p - $ pyarmor env -p set remove_docstring 1 + $ pyarmor env -p set rft_remove_docstr 1 """ import ast @@ -366,15 +365,18 @@ def __init__(self, ctx): self._rmodules = None self._builtins = None - # 在重构过程中记录类型未知,但是需要更改其属性的变量 + # Log variable name in chain attributes self.unknown_vars = [] - # 在重构过程中记录类型已知,但是属性没有在类型中定义 + # Log attribute used but not defined in class self.unknown_attrs = {} - # 在重构过程中记录使用 **kwarg 方式调用的函数列表 + # Log function which called with **kwarg self.unknown_calls = [] + # Log unknown caller with keyword arguments + self.unknown_args = [] + @property def abspath(self): return abspath(self.src) @@ -746,14 +748,16 @@ def log_unknown_var(self, module, var): self.unknown_vars.append(key) def log_unknown_call(self, func): - name = ('{%s}' % func if isinstance(func, str) else - ':'.join([func.module, '.'.join(func.scopes)])) - if name not in self.unknown_calls: - self.unknown_calls.append(name) - - def build(self, target='std'): - """Build project to generate obfuscated scripts""" - pass + if isinstance(func, str): + fields = func.split(':') + if fields[2].find('.') == -1: + self.log_unknown_var('%s:%s.%s' % fields) + elif func not in self.unknown_args: + self.unknown_args.append(func) + else: + name = ':'.join([func.module, '.'.join(func.scopes)]) + if name not in self.unknown_calls: + self.unknown_calls.append(name) def _as_dot(self): """Map project to dot graph""" From a63744a05bb3b94c62322468894306d054e25dbf Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 4 Jan 2025 12:29:37 +0800 Subject: [PATCH 135/337] Comment project attrs --- src/cli/project.py | 50 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/cli/project.py b/src/cli/project.py index 1d022c93..2dbeaf92 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -366,15 +366,61 @@ def __init__(self, ctx): self._builtins = None # Log variable name in chain attributes + # + # For example, in module "foo.py": + # + # def fa(x): + # x.runner[0].start() + # + # Because don't know the type of "x", log it as + # + # self.unknown_vars.append("foo:fa:x") + # self.unknown_vars = [] # Log attribute used but not defined in class + # + # For example, in module "foo.py": + # + # def fa(x: Fibo): + # x.items[0].start() + # + # If no found attribute "items" in class "Fibo", log it + # + # self.unknown_attrs.append("foo:Fibo:items[].start()") + # self.unknown_attrs = {} - # Log function which called with **kwarg + # Log function which called with **kwargs + # + # For example, in the module "foo.py": + # + # class Fibo: + # + # def runner(self, a=1, b=2): + # return a + b + # + # c = Fibo() + # c.runner(**data) + # + # Because the method "Fibo.runner" is called by dict + # argument "**data", log it as + # + # self.unknown_calls.append("foo:Fibo.runner") + # self.unknown_calls = [] - # Log unknown caller with keyword arguments + # Log unknown caller with keyword arguments. + # + # For example, in the module "foo.py": + # + # def fa(c): + # c.runner[2].echo(msg='hello') + # + # If don't know where "echo" is defined, log it as + # + # self.unknown_args.append("foo:fa:c.runner[].echo") + # self.unknown_args = [] @property From f1719d6fa86fa269fd0c452b118ed00f2c786af4 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 7 Jan 2025 21:25:20 +0800 Subject: [PATCH 136/337] Add core version --- src/cli/default.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 8d20617e..398ab2fe 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -7,6 +7,9 @@ patch = 8 ;; Compatible core version cli.core = 7.6.2 +mini.core = 1.0 +ecc.core = 1.0 +vmc.core = 1.0 ;; Deprecated since Pyarmor 8.2.5 ; cli.runtime = 3.2.5 From 96e6b2ebf51d1b2e20f91c0faa3dfe3cee5d284f Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 7 Jan 2025 21:25:58 +0800 Subject: [PATCH 137/337] Log unknown information --- src/cli/project.py | 80 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/src/cli/project.py b/src/cli/project.py index 2dbeaf92..1dfb94f9 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -365,6 +365,8 @@ def __init__(self, ctx): self._rmodules = None self._builtins = None + self._var_type_table = None + # Log variable name in chain attributes # # For example, in module "foo.py": @@ -383,11 +385,31 @@ def __init__(self, ctx): # For example, in module "foo.py": # # def fa(x: Fibo): - # x.items[0].start() + # x.items[0].run() + # + # If no found attribute "items" in class "Fibo", log it as + # + # self.unknown_attrs.append("foo:Fibo:x?items[].run()") # - # If no found attribute "items" in class "Fibo", log it + # Question mask "?" is placed before unknown attribute + # + # If only last attr `start` is unknown, for example: + # + # def fa(x: Fibo): + # x.runner.start() + # + # Log it as: + # + # self.unknown_attrs.append("foo:Fibo:x.runner?start()") + # + # If the first variable "x" is unknown, for example: + # + # def fa(x): + # x.items[0].run() # - # self.unknown_attrs.append("foo:Fibo:items[].start()") + # Log it as: + # + # self.unknown_attrs.append("foo:Fibo:?x.items[].run()") # self.unknown_attrs = {} @@ -421,6 +443,15 @@ def __init__(self, ctx): # # self.unknown_args.append("foo:fa:c.runner[].echo") # + # If it uses dict arguments, for example: + # + # def fa(c): + # c.runner[2].echo(**data) + # + # Log it with suffix "*" + # + # self.unknown_args.append("foo:fa:c.runner[].echo*") + # self.unknown_args = [] @property @@ -647,13 +678,13 @@ def rft_rulers(self): def rft_attr_rulers(self): """Refactor attribute rulers, for special attribute node - If can't decide variable type, use ruler for chains + If can't decide variable type, use ruler for chains - For example, "x.a", if "x" of type is unknown + For example, "x.a.b", if "x" of type is unknown - Use ruler "x.a" to rename attribute "a" + Use ruler "x.a.b" to rename attribute "a", "b" - Use ruler "!x.a" to keep attribute "a" + Use ruler "x.a().b[].c" to rename "a", "b", "c" """ value = self.rft_rulers.get('rft_attr_rulers', '') for x in value.splitlines(): @@ -663,17 +694,37 @@ def rft_attr_rulers(self): def rft_arg_rulers(self): """Refactor ruler, for arg name in Function/Call node - For example, in the call statement + For example, in the call statement kwargs = { 'msg': 'hello' } foo(**kwargs) - This kind of rule could be used to rename string `msg` + This kind of rule could be used to rename string `msg` """ value = self.rft_rulers.get('rft_arg_rulers', '') for x in value.splitlines(): yield x + @property + def var_type_table(self): + if self._var_type_table is None: + vartypes = {} + lines = self.opt('var_type_table') + for line in lines.splitlines() if lines else []: + varinfo, tname = line.split() + if varinfo.startswith('{'): + modname, varname = varinfo[1:-1].split(':', 1) + vartypes.setdefault(modname, {}) + mtypes = vartypes[modname] + mtypes.setdefault('__ivars__', {}) + mtypes['__ivars__'][varname] = tname + else: + modname, varname = varinfo.split(':', 1) + vartypes.setdefault(modname, {}) + vartypes[modname][varname] = tname + self._var_type_table = vartypes + return self._var_type_table + @property def builtins(self): if self._builtins is None: @@ -788,10 +839,13 @@ def vlist(name): logger.info('load %d modules', len(self._modules)) logger.info('load %d packages', len(self._packages)) - def log_unknown_var(self, module, var): - key = '%s:%s' % (module, var) - if key not in self.unknown_vars: - self.unknown_vars.append(key) + def log_unknown_var(self, var): + if var not in self.unknown_vars: + self.unknown_vars.append(var) + + def log_unknown_attr(self, attr): + if attr not in self.unknown_attrs: + self.unknown_vars.append(attr) def log_unknown_call(self, func): if isinstance(func, str): From e7f0b4d623a90db59867673a4f7b71a14ce50acb Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 13 Jan 2025 17:28:20 +0800 Subject: [PATCH 138/337] Refine options --- src/cli/command.py | 40 +++++++++++++++++++++++----- src/cli/model.py | 8 ++++++ src/cli/project.py | 65 +++++++++++++++++++--------------------------- 3 files changed, 68 insertions(+), 45 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index d8fa3d6f..db9d4777 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -160,6 +160,19 @@ def build_parser(self, subparsers): action="store_const", const='list', help='list project scripts, modules and packages' ) + group.add_argument( + '--types', dest='target', default='std', + action="store_const", const='types', + help=argparse.SUPPRESS + ) + group.add_argument( + '--randname', type=int, metavar='N', + help='Enable random name' + ) + group.add_argument( + '--autofix', type=int, choices=(0, 1, 2, 3), + help='Enable auto-fix mode' + ) # parser.add_argument( # '--pack', metavar='MODE', @@ -375,14 +388,27 @@ def cmd_build(self, ctx, args): logger.info(' %s', project.relsrc(s)) return - logger.info('build target %s ...', args.target) - output = args.output if args.output else 'dist' - self._build(project, args.target, output) - logger.info('build target %s end', args.target) + if args.autofix is not None: + value = args.autofix + logger.info('build auto-fix-table:%s ...', value) + output = args.output if args.output else 'dist' + self._build(project, 'autofix', output, value) + logger.info('build auto-fix-table:%s end', value) + elif args.randname is not None: + value = args.randname + logger.info('build rand-pool:%s ...', value) + output = args.output if args.output else 'dist' + self._build(project, 'namepool', output, value) + logger.info('build rand-pool:%s end', value) + else: + logger.info('build target %s ...', args.target) + output = args.output if args.output else 'dist' + self._build(project, args.target, output) + logger.info('build target %s end', args.target) - def _build(self, project, target, output): + def _build(self, project, target, output, value=None): from .core import Pytransform3 - args = [self.ctx, target, project, output] + args = [self.ctx, target, project, output, value] Pytransform3.pre_build(args) def cmd_env(self, ctx, args): @@ -479,4 +505,4 @@ def test_main(args, target='rft', log=False): if __name__ == '__main__': import sys - test_main(sys.argv[1:]) + test_main(sys.argv[1:], log=True) diff --git a/src/cli/model.py b/src/cli/model.py index eef8fe0d..90c2b5b8 100644 --- a/src/cli/model.py +++ b/src/cli/model.py @@ -356,6 +356,14 @@ class RftAttrFilter(ListOption): cls='RftNamePattern', many=True, ), + OptionModel( + name='rft_attr_rulers', + cls='RftAttrFilter', + ), + OptionModel( + name='rft_call_rulers', + cls='RftAttrFilter', + ), OptionModel( name='rft_str_keywords', cls='DictOption', diff --git a/src/cli/project.py b/src/cli/project.py index 1dfb94f9..d0c6c07c 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -389,29 +389,9 @@ def __init__(self, ctx): # # If no found attribute "items" in class "Fibo", log it as # - # self.unknown_attrs.append("foo:Fibo:x?items[].run()") + # self.unknown_attrs.append("foo:fa:x.items.run ?items") # - # Question mask "?" is placed before unknown attribute - # - # If only last attr `start` is unknown, for example: - # - # def fa(x: Fibo): - # x.runner.start() - # - # Log it as: - # - # self.unknown_attrs.append("foo:Fibo:x.runner?start()") - # - # If the first variable "x" is unknown, for example: - # - # def fa(x): - # x.items[0].run() - # - # Log it as: - # - # self.unknown_attrs.append("foo:Fibo:?x.items[].run()") - # - self.unknown_attrs = {} + self.unknown_attrs = [] # Log function which called with **kwargs # @@ -441,7 +421,7 @@ def __init__(self, ctx): # # If don't know where "echo" is defined, log it as # - # self.unknown_args.append("foo:fa:c.runner[].echo") + # self.unknown_args.append("foo:fa:c.runner.echo") # # If it uses dict arguments, for example: # @@ -450,7 +430,7 @@ def __init__(self, ctx): # # Log it with suffix "*" # - # self.unknown_args.append("foo:fa:c.runner[].echo*") + # self.unknown_args.append("foo:fa:c.runner.echo*") # self.unknown_args = [] @@ -684,9 +664,19 @@ def rft_attr_rulers(self): Use ruler "x.a.b" to rename attribute "a", "b" - Use ruler "x.a().b[].c" to rename "a", "b", "c" + Use ruler "x.a.b.c" to rename "a", "b", "c" """ - value = self.rft_rulers.get('rft_attr_rulers', '') + value = self.rft_options.get('rft_attr_rulers', '') + for x in value.splitlines(): + yield x + + @property + def rft_call_rulers(self): + """Refactor keyword argument in call statement + + If can't decide function type, use ruler to rename arg + """ + value = self.rft_options.get('rft_call_rulers', '') for x in value.splitlines(): yield x @@ -701,7 +691,7 @@ def rft_arg_rulers(self): This kind of rule could be used to rename string `msg` """ - value = self.rft_rulers.get('rft_arg_rulers', '') + value = self.rft_options.get('rft_arg_rulers', '') for x in value.splitlines(): yield x @@ -757,6 +747,9 @@ def get_module(self, qualname): def iter_module(self): """Iterate all modules in this project""" + for x in self._scripts: + yield x + for x in self._modules: yield x @@ -845,19 +838,15 @@ def log_unknown_var(self, var): def log_unknown_attr(self, attr): if attr not in self.unknown_attrs: - self.unknown_vars.append(attr) + self.unknown_attrs.append(attr) def log_unknown_call(self, func): - if isinstance(func, str): - fields = func.split(':') - if fields[2].find('.') == -1: - self.log_unknown_var('%s:%s.%s' % fields) - elif func not in self.unknown_args: - self.unknown_args.append(func) - else: - name = ':'.join([func.module, '.'.join(func.scopes)]) - if name not in self.unknown_calls: - self.unknown_calls.append(name) + if func not in self.unknown_calls: + self.unknown_calls.append(func) + + def log_unknown_arg(self, func): + if func not in self.unknown_args: + self.unknown_args.append(func) def _as_dot(self): """Map project to dot graph""" From c31248f52f5a0798c1c944f3a5d3f6da7307f015 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 14 Jan 2025 08:59:10 +0800 Subject: [PATCH 139/337] Doc `no registration code found` soluction --- docs/reference/errors.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference/errors.rst b/docs/reference/errors.rst index 6a90a475..84656ac4 100644 --- a/docs/reference/errors.rst +++ b/docs/reference/errors.rst @@ -71,6 +71,8 @@ Building Errors Please use ``pyarmor-7`` or downgrade pyarmor to 7.7.4 * - This code has been used too many times - If this code is used in CI/Docker pipeline, please send **order information** by registration email of this code to pyarmor@163.com to unlock it. Do not send this code only, it doesn't make sense. + * - no registration code found in pyarmor-regcode-xxxx.txt + - Download `pyarmor-regcode-xxxx.txt` again, check its content, make sure it's same as email body * - update license token failed - If run register command more than 3 times in 1 minute, wait for 5 minutes, and try again. From 2a8eb9b3858d41a2561daeed511821e612ec1520 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 17 Jan 2025 07:59:05 +0800 Subject: [PATCH 140/337] Refine code --- src/cli/model.py | 9 ++++++-- src/cli/project.py | 57 ++++++++++++++++++++++++++++++---------------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/cli/model.py b/src/cli/model.py index 90c2b5b8..05e8a553 100644 --- a/src/cli/model.py +++ b/src/cli/model.py @@ -339,7 +339,7 @@ class RftAttrFilter(ListOption): many=True, ), OptionModel( - name='var_type_table', + name='rft_type_rules', cls='DictOption', ), OptionModel( @@ -352,7 +352,12 @@ class RftAttrFilter(ListOption): many=True, ), OptionModel( - name='rft_exclude_args', + name='rft_exclude_funcs', + cls='RftNamePattern', + many=True, + ), + OptionModel( + name='rft_exclude_calls', cls='RftNamePattern', many=True, ), diff --git a/src/cli/project.py b/src/cli/project.py index d0c6c07c..971127a4 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -365,7 +365,9 @@ def __init__(self, ctx): self._rmodules = None self._builtins = None - self._var_type_table = None + self._rft_external_attrs = None + + self._rft_type_rules = None # Log variable name in chain attributes # @@ -408,9 +410,9 @@ def __init__(self, ctx): # Because the method "Fibo.runner" is called by dict # argument "**data", log it as # - # self.unknown_calls.append("foo:Fibo.runner") + # self.unknown_funcs.append("foo:Fibo.runner") # - self.unknown_calls = [] + self.unknown_funcs = [] # Log unknown caller with keyword arguments. # @@ -421,7 +423,7 @@ def __init__(self, ctx): # # If don't know where "echo" is defined, log it as # - # self.unknown_args.append("foo:fa:c.runner.echo") + # self.unknown_calls.append("foo:fa:c.runner.echo") # # If it uses dict arguments, for example: # @@ -430,9 +432,9 @@ def __init__(self, ctx): # # Log it with suffix "*" # - # self.unknown_args.append("foo:fa:c.runner.echo*") + # self.unknown_calls.append("foo:fa:c.runner.echo*") # - self.unknown_args = [] + self.unknown_calls = [] @property def abspath(self): @@ -528,7 +530,7 @@ def rft_options(self): Move it from rft_filter - - rft_exclude_args: list + - rft_exclude_funcs: list Move it from rft_filter @@ -538,7 +540,7 @@ def rft_options(self): When call function, solve argument not found issue - - var_type_table: dict + - rft_type_rules: dict Specify variable type @@ -604,9 +606,9 @@ def rft_exclude_names(self): yield x @property - def rft_exclude_args(self): + def rft_exclude_funcs(self): """No refactor arguments of the functions in this list""" - value = self.opt('rft_exclude_args') + value = self.opt('rft_exclude_funcs') if value: for x in value.splitlines(): yield x @@ -696,10 +698,10 @@ def rft_arg_rulers(self): yield x @property - def var_type_table(self): - if self._var_type_table is None: + def rft_type_rules(self): + if self._rft_type_rules is None: vartypes = {} - lines = self.opt('var_type_table') + lines = self.opt('rft_type_rules') for line in lines.splitlines() if lines else []: varinfo, tname = line.split() if varinfo.startswith('{'): @@ -712,8 +714,8 @@ def var_type_table(self): modname, varname = varinfo.split(':', 1) vartypes.setdefault(modname, {}) vartypes[modname][varname] = tname - self._var_type_table = vartypes - return self._var_type_table + self._rft_type_rules = vartypes + return self._rft_type_rules @property def builtins(self): @@ -722,6 +724,23 @@ def builtins(self): self._builtins = set(dir(builtins)) return self._builtins + @property + def rft_external_attrs(self): + """External attributes""" + if self._rft_external_attrs is None: + attrnames = set() + value = self.opt('rft_external_attrs') + if value: + attrnames.update(value.split()) + else: + types = list, int, str, set, tuple, float, dict + for tp in types: + attrnames.update([ + x for x in dir(tp) if x[:2] != '__' + ]) + self._rft_external_attrs = attrnames + return self._rft_external_attrs + def std_options(self): """Obfuscation options only for std target @@ -840,14 +859,14 @@ def log_unknown_attr(self, attr): if attr not in self.unknown_attrs: self.unknown_attrs.append(attr) + def log_unknown_func(self, func): + if func not in self.unknown_funcs: + self.unknown_funcs.append(func) + def log_unknown_call(self, func): if func not in self.unknown_calls: self.unknown_calls.append(func) - def log_unknown_arg(self, func): - if func not in self.unknown_args: - self.unknown_args.append(func) - def _as_dot(self): """Map project to dot graph""" modules = [x._as_dot() for x in self.modules] From d395f84429ce706af0e9b8491d82ffa10c617b5b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 23 Jan 2025 11:09:03 +0800 Subject: [PATCH 141/337] change core version to 7.6.3 --- setup.py | 2 +- src/cli/core/__init__.py | 2 +- src/cli/default.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index e6a011ab..e84d763e 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,7 @@ }, install_requires=[ - 'pyarmor.cli.core~=7.6.2' + 'pyarmor.cli.core~=7.6.3' ], entry_points={ diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index 3555ccdd..7bde41c2 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -20,7 +20,7 @@ # @Create Date: Thu Jan 12 17:29:25 CST 2023 # -__VERSION__ = '7.6.2' +__VERSION__ = '7.6.3' PLATFORM_NAMES = ( 'windows.x86_64', 'windows.x86', 'cygwin.x86_64', diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 398ab2fe..f870dfb4 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -6,7 +6,7 @@ minor = 0 patch = 8 ;; Compatible core version -cli.core = 7.6.2 +cli.core = 7.6.3 mini.core = 1.0 ecc.core = 1.0 vmc.core = 1.0 From 09c12fdaa05cf60d5b50575fa3120b74bca79127 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 23 Jan 2025 20:30:53 +0800 Subject: [PATCH 142/337] change core version to 7.6.4 --- setup.py | 2 +- src/cli/core/__init__.py | 2 +- src/cli/default.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index e84d763e..f5f6634a 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,7 @@ }, install_requires=[ - 'pyarmor.cli.core~=7.6.3' + 'pyarmor.cli.core~=7.6.4' ], entry_points={ diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index 7bde41c2..427738b9 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -20,7 +20,7 @@ # @Create Date: Thu Jan 12 17:29:25 CST 2023 # -__VERSION__ = '7.6.3' +__VERSION__ = '7.6.4' PLATFORM_NAMES = ( 'windows.x86_64', 'windows.x86', 'cygwin.x86_64', diff --git a/src/cli/default.cfg b/src/cli/default.cfg index f870dfb4..abfe15b8 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -6,7 +6,7 @@ minor = 0 patch = 8 ;; Compatible core version -cli.core = 7.6.3 +cli.core = 7.6.4 mini.core = 1.0 ecc.core = 1.0 vmc.core = 1.0 From b5dc51cada86f1c56321d07a76306cb40b11d341 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 12 Feb 2025 21:45:18 +0800 Subject: [PATCH 143/337] Fix termux example errors --- docs/tutorial/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/installation.rst b/docs/tutorial/installation.rst index 45b9689a..0ca1bc96 100644 --- a/docs/tutorial/installation.rst +++ b/docs/tutorial/installation.rst @@ -170,8 +170,8 @@ Termux issues In Termux, after installation it need patch extensions. For example:: - $ patchelf --add-needed /data/data/com.termux/files/usr/lib/python3.11/site-packages/pyarmor/cli/core/android/aarch64/pytransform3.so - $ patchelf --add-needed /data/data/com.termux/files/usr/lib/python3.11/site-packages/pyarmor/cli/core/android/aarch64/pyarmor_runtime.so + $ patchelf --add-needed libpython3.11.so.0.1 /data/data/com.termux/files/usr/lib/python3.11/site-packages/pyarmor/cli/core/android/aarch64/pytransform3.so + $ patchelf --add-needed libpython3.11.so.0.1 /data/data/com.termux/files/usr/lib/python3.11/site-packages/pyarmor/cli/core/android/aarch64/pyarmor_runtime.so Sometimes, it need set runpath too. For example:: From fdc5726041ad594b0764cf5980db172d70828001 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 12 Feb 2025 21:45:56 +0800 Subject: [PATCH 144/337] Add note for Nuitka --- docs/how-to/third-party.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/how-to/third-party.rst b/docs/how-to/third-party.rst index 17177f72..543ae48f 100644 --- a/docs/how-to/third-party.rst +++ b/docs/how-to/third-party.rst @@ -155,8 +155,6 @@ First disable restrict mode:: $ pyarmor cfg restrict_module=0 -Now disable restrict_module, run the nuitka script may raise ``RuntimeError: unauthorized use of script`` - Next use default options to obfuscate the scripts:: $ pyarmor gen foo.py @@ -165,6 +163,14 @@ Finally nuitka the obfuscated script ``dist/foo.py``, check whether it works or Try more options, but I think restrict options such as :option:`--private`, :option:`--restrict`, :option:`--assert-call`, :option:`--assert-import` may not work. +Note that extension `pyarmor_runtime.so` must be in the package. For example:: + + $ ls dist/pyarmor_runtime_000000 + ... __init__.py + ... pyarmor_runtime.so + +If Nuitka convert `__init__.py` to `pyarmor_runtime_000000_init_.py`, and copy `pyarmor_runtime.so` to same path, it also raises ``RuntimeError: unauthorized use of script`` + streamlit --------- From a891b57b30850a35060ead0127a84f02a7ef3d4b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 13 Feb 2025 06:51:25 +0800 Subject: [PATCH 145/337] Fix link error --- docs/ChangeLogs.8 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/ChangeLogs.8 b/docs/ChangeLogs.8 index c35e8f6c..59351361 100755 --- a/docs/ChangeLogs.8 +++ b/docs/ChangeLogs.8 @@ -71,6 +71,7 @@ There are 3 cases for old users after Pyarmor 8.0 is released: Python 3.7+ Refer to -https://github.com/dashingsoft/pyarmor#import-notes-for-pyarmor-prior-to-80 +https://github.com/dashingsoft/pyarmor/tree/v8.0#import-notes-for-pyarmor-prior-to-80 +https://github.com/dashingsoft/pyarmor/releases/tag/v8.0 [licenses]: https://pyarmor.readthedocs.io/en/latest/licenses.html From f13d835b9021e628d9a2b7838e67de67b78c7805 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 13 Feb 2025 06:54:43 +0800 Subject: [PATCH 146/337] Refine doc --- docs/how-to/third-party.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/how-to/third-party.rst b/docs/how-to/third-party.rst index 543ae48f..4966d1c8 100644 --- a/docs/how-to/third-party.rst +++ b/docs/how-to/third-party.rst @@ -163,13 +163,15 @@ Finally nuitka the obfuscated script ``dist/foo.py``, check whether it works or Try more options, but I think restrict options such as :option:`--private`, :option:`--restrict`, :option:`--assert-call`, :option:`--assert-import` may not work. -Note that extension `pyarmor_runtime.so` must be in the package. For example:: +.. note:: - $ ls dist/pyarmor_runtime_000000 - ... __init__.py - ... pyarmor_runtime.so + Before v9.1.0, the extension `pyarmor_runtime.so` must be in the package. For example:: -If Nuitka convert `__init__.py` to `pyarmor_runtime_000000_init_.py`, and copy `pyarmor_runtime.so` to same path, it also raises ``RuntimeError: unauthorized use of script`` + $ ls dist/pyarmor_runtime_000000 + ... __init__.py + ... pyarmor_runtime.so + + If Nuitka convert `__init__.py` to `pyarmor_runtime_000000_init_.py`, and copy `pyarmor_runtime.so` to same path, it also raises ``RuntimeError: unauthorized use of script`` streamlit --------- From 63bdb4578fe0345004501460b53e94c5ec1f4e42 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 13 Feb 2025 10:48:20 +0800 Subject: [PATCH 147/337] Fix rtds issue --- .readthedocs.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index f61cd63b..043e2783 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -5,6 +5,9 @@ build: tools: python: "3.7" +sphinx: + configuration: docs/conf.py + python: install: - requirements: docs/requirements.txt From eabf376234105796ec1d276a1527fc414b7e6605 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 13 Feb 2025 10:55:53 +0800 Subject: [PATCH 148/337] Refine doc --- docs/how-to/third-party.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/how-to/third-party.rst b/docs/how-to/third-party.rst index 4966d1c8..c7516998 100644 --- a/docs/how-to/third-party.rst +++ b/docs/how-to/third-party.rst @@ -165,13 +165,7 @@ Try more options, but I think restrict options such as :option:`--private`, :opt .. note:: - Before v9.1.0, the extension `pyarmor_runtime.so` must be in the package. For example:: - - $ ls dist/pyarmor_runtime_000000 - ... __init__.py - ... pyarmor_runtime.so - - If Nuitka convert `__init__.py` to `pyarmor_runtime_000000_init_.py`, and copy `pyarmor_runtime.so` to same path, it also raises ``RuntimeError: unauthorized use of script`` + It also requires v9.1.0+. Before v9.1.0, Nuitka will convert package `pyarmor_runtime_000000/__init__.py` to `pyarmor_runtime_000000_init_.py`, it also results in ``RuntimeError: unauthorized use of script`` streamlit --------- From 6a514b8aa16eba0cd14ccccca9799a6e4259e490 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 13 Feb 2025 11:01:29 +0800 Subject: [PATCH 149/337] Refine doc --- docs/how-to/third-party.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/third-party.rst b/docs/how-to/third-party.rst index c7516998..aed04653 100644 --- a/docs/how-to/third-party.rst +++ b/docs/how-to/third-party.rst @@ -165,7 +165,7 @@ Try more options, but I think restrict options such as :option:`--private`, :opt .. note:: - It also requires v9.1.0+. Before v9.1.0, Nuitka will convert package `pyarmor_runtime_000000/__init__.py` to `pyarmor_runtime_000000_init_.py`, it also results in ``RuntimeError: unauthorized use of script`` + It also requires v9.1.0+. Because Nuitka will convert package `pyarmor_runtime_000000/__init__.py` to `pyarmor_runtime_000000_init_.py`, it also results in ``RuntimeError: unauthorized use of script``, this is fixed in v9.1.0 (non-trial version). streamlit --------- From 98360e963aed91c1d2c61c16b2493b723b24aaca Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 15 Feb 2025 08:06:27 +0800 Subject: [PATCH 150/337] Delay deadline for new features --- docs/ReleasePlan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md index 2bfbb21d..aa8a9837 100755 --- a/docs/ReleasePlan.md +++ b/docs/ReleasePlan.md @@ -110,4 +110,4 @@ Status: plan, released on 2025-12-01, 2 months after Python 3.14 is released - Support Python 3.14 which will be released on 2025-10-01 as plan -Pyarmor features will be stable around Dec. 31, 2024 +Pyarmor features will be stable in 2025 From c384bcf3080888700768f9a07cad922592f541a2 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 17 Feb 2025 14:47:57 +0800 Subject: [PATCH 151/337] Refine doc --- docs/how-to/third-party.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/third-party.rst b/docs/how-to/third-party.rst index aed04653..d2919402 100644 --- a/docs/how-to/third-party.rst +++ b/docs/how-to/third-party.rst @@ -165,7 +165,7 @@ Try more options, but I think restrict options such as :option:`--private`, :opt .. note:: - It also requires v9.1.0+. Because Nuitka will convert package `pyarmor_runtime_000000/__init__.py` to `pyarmor_runtime_000000_init_.py`, it also results in ``RuntimeError: unauthorized use of script``, this is fixed in v9.1.0 (non-trial version). + It may requires v9.0.8+ and non-trial version. Because Nuitka will convert package `pyarmor_runtime_000000/__init__.py` to `pyarmor_runtime_000000_init_.py`, it also results in ``RuntimeError: unauthorized use of script``, this is fixed in v9.0.8 streamlit --------- From b707621c895e3947f462ff336af4087b6794fccd Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 18 Feb 2025 05:33:33 +0800 Subject: [PATCH 152/337] Doc how to query hardware information in bootstrap --- docs/reference/environments.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/reference/environments.rst b/docs/reference/environments.rst index fe59b307..7e67cac6 100644 --- a/docs/reference/environments.rst +++ b/docs/reference/environments.rst @@ -303,6 +303,15 @@ Check it, first copy this script to ``.pyarmor/hooks/pyarmor_runtime.py``:: ... RuntimeError: unauthorized use of script (1:10325) +If need query hardware information, the simple way is to import Pyarmor extension `pytransform3`. For example, in Windows, copy the corresponding `pytransform3.pyd` to target device, then get machine id by this way + +.. code-block:: python + + def bootstrap(user_data): + from pytransform3 import get_hd_info + # Refer to pyarmor/cli/get_hd_info.py + print('Machine ID: %s' % get_hd_info(22).decode()) + .. _target environments: ===================== From e8cb7211ba321182bab161d1bb92b2932ee864ae Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 18 Feb 2025 10:30:29 +0800 Subject: [PATCH 153/337] Change version to 9.1.0 --- docs/conf.py | 4 ++-- src/cli/__init__.py | 2 +- src/cli/default.cfg | 4 ++-- src/config.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index b2abf94b..669d7ed0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,9 +57,9 @@ # built documents. # # The short X.Y version. -version = '9.0' +version = '9.1' # The full version, including alpha/beta/rc tags. -release = '9.0.8' +release = '9.1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index a01e505a..e788167e 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.0.8' +__VERSION__ = '9.1.0' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index abfe15b8..de15a40a 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -2,8 +2,8 @@ ;; Pyarmor version major = 9 -minor = 0 -patch = 8 +minor = 1 +patch = 0 ;; Compatible core version cli.core = 7.6.4 diff --git a/src/config.py b/src/config.py index c7ebee94..596befd0 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.0.8' +version = '9.1.0' # The corresponding version of pytransform.so core_version = 'r52.6' From 56c5495a7e032178b7bf5f925691fa95a10fca8c Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 21 Feb 2025 20:36:26 +0800 Subject: [PATCH 154/337] Add 9.1 new features --- README.md | 34 ++++++++++++++++++++-------------- docs/conf.py | 4 ++-- docs/index.rst | 10 ++++++++++ docs/reference/man.rst | 27 +++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e0b21ba9..528a2047 100644 --- a/README.md +++ b/README.md @@ -66,31 +66,26 @@ Please read the [Pyarmor EULA](LICENSE). ## Getting Help -1. **Consult the [Pyarmor Documentation][doc].** -2. **Check the [FAQ][faq] for answers to common questions.** -3. **Try the documentation [index][genindex] or the [detailed table of contents][mastertoc].** -4. **If you still can't find the information you need, see [asking questions on GitHub][asking].** -5. **[Report bugs][issues] following the issue template.** -6. **For business and security inquiries, send an email to .** +- **[Ask in learning system][askeke] or [look through check list][checklist]** +- **Consult the [Pyarmor Documentation][doc].** +- **Check the [FAQ][faq] for answers to common questions.** +- **Try the documentation [index][genindex] or the [detailed table of contents][mastertoc].** +- **If you still can't find the information you need, see [asking questions on GitHub][asking].** +- **[Report bugs][issues] following the issue template.** +- **For business and security inquiries, send an email to .** There are also some third-party learn platforms (not Pyarmor offical) - **[Ask Pyarmor Guru][gurubase], it is a Pyarmor-focused AI to answer your questions** -[faq]: https://pyarmor.readthedocs.io/en/latest/questions.html -[issues]: https://github.com/dashingsoft/pyarmor/issues -[genindex]: https://pyarmor.readthedocs.io/en/stable/genindex.html -[mastertoc]: https://pyarmor.readthedocs.io/en/stable/index.html#table-of-contents -[asking]: https://pyarmor.readthedocs.io/en/latest/questions.html#asking-questions-in-github -[doc]: https://pyarmor.readthedocs.io/ -[gurubase]: https://gurubase.io/g/pyarmor - ## Resources * [Website](https://pyarmor.dashingsoft.com) * [Documentation][doc] * [Documentation 8.x](https://pyarmor.readthedocs.io/en/v8.5.12/) * [Documentation 7.x](https://pyarmor.readthedocs.io/en/v7.7/) +* [Pyarmor 9.1 new features](https://eke.dashingsoft.com/pyarmor/docs/en/index.html) +* [Pyarmor Learning System](https://eke.dashingsoft.com/pyarmor/) 中文资源 @@ -98,6 +93,8 @@ There are also some third-party learn platforms (not Pyarmor offical) * [Pyarmor 在线文档](https://pyarmor.readthedocs.io/zh/latest/) * [Pyarmor 8.x 在线文档](https://pyarmor.readthedocs.io/zh/v8.5.12/) * [Pyarmor 7.x 在线文档](https://pyarmor.readthedocs.io/zh/v7.x/) +* [Pyarmor 9.1 新功能](https://eke.dashingsoft.com/pyarmor/docs/zh/index.html) +* [Pyarmor 学习系统](https://eke.dashingsoft.com/pyarmor/) ## Changelog @@ -113,3 +110,12 @@ Make sure to read the changelog carefully before upgrading Pyarmor: **Upcoming features** at [Pyarmor Release Plan](docs/ReleasePlan.md) [releases]: https://github.com/dashingsoft/pyarmor/releases +[faq]: https://pyarmor.readthedocs.io/en/latest/questions.html +[issues]: https://github.com/dashingsoft/pyarmor/issues +[genindex]: https://pyarmor.readthedocs.io/en/stable/genindex.html +[mastertoc]: https://pyarmor.readthedocs.io/en/stable/index.html#table-of-contents +[asking]: https://pyarmor.readthedocs.io/en/latest/questions.html#asking-questions-in-github +[doc]: https://pyarmor.readthedocs.io/ +[gurubase]: https://gurubase.io/g/pyarmor +[askeke]: https://eke.dashingsoft.com/pyarmor/ask +[checklist]: https://pyarmor.readthedocs.io/en/latest/reference/solutions.html diff --git a/docs/conf.py b/docs/conf.py index 669d7ed0..b2abf94b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,9 +57,9 @@ # built documents. # # The short X.Y version. -version = '9.1' +version = '9.0' # The full version, including alpha/beta/rc tags. -release = '9.1.0' +release = '9.0.8' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/index.rst b/docs/index.rst index 0f6106f9..7622d0ed 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,6 +23,16 @@ How the documentation is organized * :doc:`Part 5: Licenses ` describes EULA of |Pyarmor|, the different |Pyarmor| licenses and how to purchase |Pyarmor| license. +.. important:: + + New features introduced by Pyarmor 9.1.0 are moved to Pyarmor Learning System + + https:://eke.dashingsoft.com/pyarmor/docs/en/index.html + + It includes 3 commands: `pyarmor init`, `pyarmor env`, `pyarmor build` + + And new obfuscated script types: `rft`, `mini`, `vmc`, `ecc` + Getting help ============ diff --git a/docs/reference/man.rst b/docs/reference/man.rst index 1df75a30..f763cba9 100644 --- a/docs/reference/man.rst +++ b/docs/reference/man.rst @@ -1019,6 +1019,33 @@ Not all the old license could be upgrade to new license, check :doc:`../licenses Valid value is from 1 to 100 +.. _pyarmor init: + +pyarmor init +============ + +.. versionadded:: 9.1 + +Refer to https://eke.dashingsoft.com/pyarmor/docs/en/user/man.html + +.. _pyarmor env: + +pyarmor env +=========== + +.. versionadded:: 9.1 + +Refer to https://eke.dashingsoft.com/pyarmor/docs/en/user/man.html + +.. _pyarmor build: + +pyarmor build +============= + +.. versionadded:: 9.1 + +Refer to https://eke.dashingsoft.com/pyarmor/docs/en/user/man.html + Environment Variables ===================== From 158be4a8c3ea50a0dacab6e2398fae9eb167da01 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 21 Feb 2025 20:36:45 +0800 Subject: [PATCH 155/337] Refine new features --- src/cli/command.py | 17 ++-- src/cli/model.py | 49 ++++------ src/cli/project.py | 236 ++++++++++++++++++++++++++++++++------------- src/cli/shell.py | 2 +- 4 files changed, 202 insertions(+), 102 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index db9d4777..100d8d64 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -128,7 +128,7 @@ def build_parser(self, subparsers): ) group = parser.add_argument_group( - 'select build target (default: std)' + 'select build target' ).add_mutually_exclusive_group() # group.add_argument( # '--std', dest='target', default='std', @@ -145,14 +145,19 @@ def build_parser(self, subparsers): # action="store_const", const='vmc', # help='genetate scripts with vm C code' # ) + group.add_argument( + '--rft', dest='target', default='std', + action="store_const", const='rft', + help='only refactor scripts' + ) group.add_argument( '--mini', dest='target', default='std', action="store_const", const='mini', help='genetate high performance scripts' ) group.add_argument( - '--rft', dest='target', default='std', - action="store_const", const='rft', + '--mini-rft', dest='target', default='std', + action="store_const", const='mini-rft', help='only refactor scripts' ) group.add_argument( @@ -166,12 +171,12 @@ def build_parser(self, subparsers): help=argparse.SUPPRESS ) group.add_argument( - '--randname', type=int, metavar='N', - help='Enable random name' + '--randname', type=int, metavar='{0,1}', + help='Build random name pool' ) group.add_argument( '--autofix', type=int, choices=(0, 1, 2, 3), - help='Enable auto-fix mode' + help='Generate rft rules by autofix mode' ) # parser.add_argument( diff --git a/src/cli/model.py b/src/cli/model.py index 05e8a553..b6b7df4c 100644 --- a/src/cli/model.py +++ b/src/cli/model.py @@ -191,13 +191,12 @@ class RftArgEnum(EnumOption): """How to rename argument - 0: no rename arguments - - 1: rename all arguments - - min: only rename posonly, vararg and kwarg - - !keyword: no rename keyword only arguments - - !default: no rename argument which has default and kwarg + - 1: rename posonly arguments + - 2: rename kwonly arguments + - 3: rename all arguments """ - CHOICES = '0', '1', '2', '3', 'no', 'min', '!kw', 'all' + CHOICES = '0', '1', '2', '3' class RftNamePattern(TextOption): @@ -302,34 +301,29 @@ class RftAttrFilter(ListOption): ), ], - 'rft_option': [ + 'rft': [ OptionModel( - name='rft_remove_assert', + name='remove_assert', cls='BoolOption', value=0, ), OptionModel( - name='rft_remove_docstr', + name='remove_docstr', cls='BoolOption', value=0, ), OptionModel( - name='rft_builtin', + name='builtin_mode', cls='BoolOption', value=0, ), OptionModel( - name='rft_argument', + name='argument_mode', cls='RftArgEnum', - value='1', + value='3', ), OptionModel( - name='on_unknown_attr', - cls='RftAttrEnum', - value='log', - ), - OptionModel( - name='rft_auto_export', + name='auto_export_mode', cls='BoolOption', value=0, ), @@ -339,40 +333,37 @@ class RftAttrFilter(ListOption): many=True, ), OptionModel( - name='rft_type_rules', + name='var_types', cls='DictOption', ), OptionModel( - name='extra_type_info', - cls='DictOption', + name='exclude_names', + cls='RftNamePattern', + many=True, ), OptionModel( - name='rft_exclude_names', + name='exclude_funcs', cls='RftNamePattern', many=True, ), OptionModel( - name='rft_exclude_funcs', + name='external_types', cls='RftNamePattern', many=True, ), OptionModel( - name='rft_exclude_calls', + name='external_attrs', cls='RftNamePattern', many=True, ), OptionModel( - name='rft_attr_rulers', + name='attr_rules', cls='RftAttrFilter', ), OptionModel( - name='rft_call_rulers', + name='call_rules', cls='RftAttrFilter', ), - OptionModel( - name='rft_str_keywords', - cls='DictOption', - ), ], 'rft_filter': [ OptionModel( diff --git a/src/cli/project.py b/src/cli/project.py index 971127a4..55aac237 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -23,7 +23,7 @@ # # - Define project object for Pyarmor 9. # - Define project commands: init, build -# - Define 3 targets: std, mini, plain +# - Define targets: std, fly, vmc, ecc, rft """Manage projects @@ -45,16 +45,6 @@ rpath = path path -[project.name] -name = -path = - -modules = -packages = 0 or 1 -excludes = - -rpath = - Examples -------- @@ -70,7 +60,7 @@ 3. Config project and print project information $ pyarmor env -p - $ pyarmor env -p set rft_remove_docstr 1 + $ pyarmor env -p set rft:remove_docstr 1 """ import ast @@ -79,8 +69,9 @@ import os import tokenize -from fnmatch import fnmatch from collections import namedtuple +from fnmatch import fnmatch +from json import loads as jsonloads, load as jsonload from os.path import ( abspath, basename, exists, isabs, join as joinpath, normpath, relpath, splitext @@ -89,7 +80,7 @@ from textwrap import dedent -logger = logging.getLogger('cli.project') +logger = logging.getLogger('cli.build') GRAPHVIZ_INDENT = ' ' @@ -349,6 +340,8 @@ class Project: """ + ATTR_LOGFILE = '.pyarmor/project/rft_unknown_attrs.log' + def __init__(self, ctx): self.ctx = ctx self.src = '' @@ -365,9 +358,9 @@ def __init__(self, ctx): self._rmodules = None self._builtins = None - self._rft_external_attrs = None - self._rft_type_rules = None + self._rft_include_attrs = None + self._used_external_types = None # Log variable name in chain attributes # @@ -380,7 +373,7 @@ def __init__(self, ctx): # # self.unknown_vars.append("foo:fa:x") # - self.unknown_vars = [] + # self.unknown_vars = [] # Log attribute used but not defined in class # @@ -393,7 +386,7 @@ def __init__(self, ctx): # # self.unknown_attrs.append("foo:fa:x.items.run ?items") # - self.unknown_attrs = [] + self.unknown_attrs = set() # Log function which called with **kwargs # @@ -436,6 +429,19 @@ def __init__(self, ctx): # self.unknown_calls = [] + # Log all attributes which is external base class attr + # + # For example: + # + # class C(dict): + # + # def merge(self, another): + # super().merge(another) + # + # "merge" will be loged to external_attrs + # self.external_attrs = [] + + @property def abspath(self): return abspath(self.src) @@ -472,15 +478,15 @@ def namespaces(self): def rft_options(self): """Refactor options: - - rft_remove_assert: bool + - remove_assert: bool If 1, remove assert node - - rft_remove_docstr: bool + - remove_docstr: bool If 1, remove all docstring - - rft_builtin: bool + - builtin_mode: bool 0, do not touch any builtin names 1, as build target, maybe std, mini or plain @@ -491,9 +497,9 @@ def rft_options(self): - rft_ximport: bool - always 0, do not touch node "from..import *" + Always 0, do not touch node "from..import *" - - rft_argument: enum('no', 'pos', '!kw', 'all') + - argument_mode: enum('0', '1', '2', '3') 0: "no", no reform any argument node 1: "pos", reform posonly arguments @@ -522,15 +528,19 @@ def rft_options(self): 1, reform string by rft_string_filters 2, reform all string - - rft_auto_export: bool + - auto_export_mode: bool - Auto export all names in module.__all__ + True: auto export all names in module.__all__ - - rft_exclude_names: list + If class is exported, all class members are exported + If function is exported, arguments can't be renamed + Only module variable can be exported separately + + - exclude_names: list Move it from rft_filter - - rft_exclude_funcs: list + - exclude_funcs: list Move it from rft_filter @@ -574,7 +584,7 @@ def rft_options(self): """ if self._rft_options is None: cfg = self.ctx.cfg - sect = 'rft_option' + sect = 'rft' if cfg.has_section(sect): self._rft_options = dict(cfg.items(sect)) else: @@ -597,18 +607,16 @@ def rft_exclude_names(self): Each ruler must start with package or module name It supports pattern match as fnmatchcase Pattern only match one level - - It's also used to export names manually """ - value = self.opt('rft_exclude_names') + value = self.opt('exclude_names') if value: for x in value.splitlines(): yield x @property def rft_exclude_funcs(self): - """No refactor arguments of the functions in this list""" - value = self.opt('rft_exclude_funcs') + """No touch arguments for listed functions""" + value = self.opt('exclude_funcs') if value: for x in value.splitlines(): yield x @@ -657,34 +665,38 @@ def rft_rulers(self): return self._rft_rulers @property - def rft_attr_rulers(self): - """Refactor attribute rulers, for special attribute node + def rft_attr_rules(self): + """Refactor attribute rules, for special attribute node - If can't decide variable type, use ruler for chains + If can't decide variable type, use rule for chains For example, "x.a.b", if "x" of type is unknown - Use ruler "x.a.b" to rename attribute "a", "b" + Use rule "x.a.b" to rename attribute "a", "b" - Use ruler "x.a.b.c" to rename "a", "b", "c" + Use rule "x.a.b.c" to rename "a", "b", "c" + + Use rule "x.a.b -> *.?.*" to rename "a" only + + Use rule "*.write -> *.write" to keep all write attribute """ - value = self.rft_options.get('rft_attr_rulers', '') + value = self.rft_options.get('attr_rules', '') for x in value.splitlines(): yield x @property - def rft_call_rulers(self): + def rft_call_rules(self): """Refactor keyword argument in call statement If can't decide function type, use ruler to rename arg """ - value = self.rft_options.get('rft_call_rulers', '') + value = self.rft_options.get('call_rules', '') for x in value.splitlines(): yield x @property - def rft_arg_rulers(self): - """Refactor ruler, for arg name in Function/Call node + def rft_arg_rules(self): + """Refactor rule, for arg name in Function/Call node For example, in the call statement @@ -693,19 +705,32 @@ def rft_arg_rulers(self): This kind of rule could be used to rename string `msg` """ - value = self.rft_options.get('rft_arg_rulers', '') + value = self.rft_options.get('rft_arg_rules', '') for x in value.splitlines(): yield x @property def rft_type_rules(self): + """Specify variable/blockvar type + + Support format: + + modname:scope:var typename + modname:scope:var.[?] typename + modname:scope:func.() typename + modname:scope:cls.method.() typename + + For blockvar in For/Comprehension/With + modname:scope:{var} typename + """ if self._rft_type_rules is None: vartypes = {} - lines = self.opt('rft_type_rules') + lines = self.opt('var_types') for line in lines.splitlines() if lines else []: varinfo, tname = line.split() - if varinfo.startswith('{'): - modname, varname = varinfo[1:-1].split(':', 1) + if varinfo.endswith('}'): + info = varinfo[:-1].replace('{', '') + modname, varname = info.split(':', 1) vartypes.setdefault(modname, {}) mtypes = vartypes[modname] mtypes.setdefault('__ivars__', {}) @@ -724,22 +749,56 @@ def builtins(self): self._builtins = set(dir(builtins)) return self._builtins + @property + def used_external_types(self): + """Used external types""" + if self._used_external_types is None: + used_types = {} + names = self.opt('rft_external_types') + if names is None: + names = [ + 'builtins', + '.pyarmor/project/rft_external_types.json' + ] + for name in names: + if name.endswith('.json'): + if exists(name): + with open(name) as f: + used_types.update(jsonload(f)) + else: + m = name.split('.')[0] + used_types.update(self._get_external_types(m)) + self._used_external_types = used_types + return self._used_external_types + + @property + def rft_external_types(self): + """External types manual""" + value = self.rft_options.get('external_types', '') + if value: + for x in value: + yield x + @property def rft_external_attrs(self): - """External attributes""" - if self._rft_external_attrs is None: - attrnames = set() - value = self.opt('rft_external_attrs') + """External attrs manual + x means append to attrs of rft_external_types + !x means exclude x from attrs of rft_external_types + """ + value = self.rft_options.get('external_attrs', '') + if value: + for x in value: + yield x + + @property + def rft_include_attrs(self): + """List attributes need to be renamed on unknown type""" + if self._rft_include_attrs is None: + self._rft_include_attrs = set() + value = self.rft_options.get('include_attrs', '') if value: - attrnames.update(value.split()) - else: - types = list, int, str, set, tuple, float, dict - for tp in types: - attrnames.update([ - x for x in dir(tp) if x[:2] != '__' - ]) - self._rft_external_attrs = attrnames - return self._rft_external_attrs + self._rft_include_attrs.update(value.split()) + return self._rft_include_attrs def std_options(self): """Obfuscation options only for std target @@ -851,13 +910,20 @@ def vlist(name): logger.info('load %d modules', len(self._modules)) logger.info('load %d packages', len(self._packages)) - def log_unknown_var(self, var): - if var not in self.unknown_vars: - self.unknown_vars.append(var) + def start(self): + self._logfile = open(self.ATTR_LOGFILE, 'w') - def log_unknown_attr(self, attr): - if attr not in self.unknown_attrs: - self.unknown_attrs.append(attr) + def stop(self): + self._logfile.close() + + def log_unknown_attr(self, line): + fields = line.split(':') + attrs = fields[2].split('.') + start = int(fields[3]) + self.unknown_attrs.update([ + x for x in attrs[start:] if x[:1] not in ('(', '[') + ]) + self._logfile.write(line + '\n') def log_unknown_func(self, func): if func not in self.unknown_funcs: @@ -867,6 +933,44 @@ def log_unknown_call(self, func): if func not in self.unknown_calls: self.unknown_calls.append(func) + def _get_external_types(self, modname, pypaths=None): + from sys import executable + from subprocess import check_output, CalledProcessError + source = Template(dedent("""\ + import json + import sys + x = ${pypaths} + if x: + sys.path[0:0] = [x] if isinstance(x, str) else x + import ${name} + typeinfo = {'${name}': []} + for key, value in ${name}.__dict__.items(): + if key[:2] == '__': + continue + if isinstance(value, type): + typeinfo['${name}.%s' % key] = [ + x for x in dir(value) if x[:2] != '__' + ] + typeinfo['${name}'].append(key) + print(json.dumps(typeinfo)) + """)).substitute(name=modname, pypaths=repr(pypaths)) + + try: + output = check_output([executable, '-c', source]) + return jsonloads(output) + except CalledProcessError: + pass + + def get_external_type(self, modname, clsname=None): + extypes = self.used_external_types + if modname not in extypes: + result = self._get_external_types(modname) + if result: + extypes.update(result) + if clsname is None: + return extypes + return extypes.get('%s.%s' % (modname, clsname), []) + def _as_dot(self): """Map project to dot graph""" modules = [x._as_dot() for x in self.modules] diff --git a/src/cli/shell.py b/src/cli/shell.py index a89f76ea..d21ad2e7 100644 --- a/src/cli/shell.py +++ b/src/cli/shell.py @@ -280,7 +280,7 @@ def _init_domains(self): for name in ('filter', 'bcc'): domain.add_group(name, GROUPS[name]) - ps.add_section('rft_option') + ps.add_section('rft') return {'global': gs, 'local': ls, 'project': ps} From 0a2f0fe88ac3dc42646ac0331a05cf8bbd75db71 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 21 Feb 2025 22:27:16 +0800 Subject: [PATCH 156/337] Fix bug --- src/cli/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 75db1745..cf442068 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -709,7 +709,7 @@ def log_settings(ctx, args): def log_bug(e): - parser = main_parser() + parser = main_parser(cmd=Commander()) args = parser.parse_args(sys.argv[1:]) ctx = Context(*get_home_paths(args)) cmdline = ' '.join(sys.argv) From e672cf88fa05ee67553eb10a8520ce8c95115270 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 21 Feb 2025 22:27:49 +0800 Subject: [PATCH 157/337] Fix issue #2069 --- src/cli/__main__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index cf442068..a42e6141 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -206,7 +206,9 @@ def check_gen_context(ctx, args): if ctx.runtime_outer and any( [ctx.runtime_devices, ctx.runtime_period, ctx.runtime_expired]): - raise CliError('--outer conflicts with any -e, --period, -b') + # Fix issue 2069 + if args.inputs[0].lower() not in ('key', 'k'): + raise CliError('--outer conflicts with any -e, --period, -b') if args.pack: choices = 'onefile', 'onedir', 'F', 'D', 'FC', 'DC' From f773196a419b36977849967b7381b254a80714b4 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 22 Feb 2025 21:26:33 +0800 Subject: [PATCH 158/337] Fix bugs --- src/cli/command.py | 22 +++++++--------------- src/cli/project.py | 15 +++++++-------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index 100d8d64..9403bf3e 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -158,7 +158,7 @@ def build_parser(self, subparsers): group.add_argument( '--mini-rft', dest='target', default='std', action="store_const", const='mini-rft', - help='only refactor scripts' + help='genetate high performance refactor scripts' ) group.add_argument( '--list', dest='target', default='std', @@ -176,7 +176,7 @@ def build_parser(self, subparsers): ) group.add_argument( '--autofix', type=int, choices=(0, 1, 2, 3), - help='Generate rft rules by autofix mode' + help='Generate refactor rules by autofix mode' ) # parser.add_argument( @@ -348,14 +348,6 @@ def cmd_build(self, ctx, args): pyarmor build --mini - Generate standard obfuscated scirpts:: - - pyarmor build --std - - Generate standard obfuscated scirpts with extra options:: - - pyarmor build --std --assert-import --restrict 2 - """ logger.debug('build %s', args) cfg = configparser.ConfigParser( @@ -425,23 +417,23 @@ def cmd_env(self, ctx, args): Show project src - pyarmor env get src + pyarmor env -p get src Change project src - pyarmor env set src ../src + pyarmor env -p set src ../src Append project excludes - pyarmor env push excludes "test*" + pyarmor env -p push excludes "test*" Remove project excludes - pyarmor env pop excludes "test*" + pyarmor env -p pop excludes "test*" Clear project excludes - pyarmor env reset excludes + pyarmor env -p reset excludes """ logger.debug('env %s', args) diff --git a/src/cli/project.py b/src/cli/project.py index 55aac237..222c1285 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -441,7 +441,6 @@ def __init__(self, ctx): # "merge" will be loged to external_attrs # self.external_attrs = [] - @property def abspath(self): return abspath(self.src) @@ -591,7 +590,7 @@ def rft_options(self): self._rft_options = {} return self._rft_options - def opt(self, name): + def rft_opt(self, name): return self.rft_options.get(name) @property @@ -608,7 +607,7 @@ def rft_exclude_names(self): It supports pattern match as fnmatchcase Pattern only match one level """ - value = self.opt('exclude_names') + value = self.rft_opt('exclude_names') if value: for x in value.splitlines(): yield x @@ -616,7 +615,7 @@ def rft_exclude_names(self): @property def rft_exclude_funcs(self): """No touch arguments for listed functions""" - value = self.opt('exclude_funcs') + value = self.rft_opt('exclude_funcs') if value: for x in value.splitlines(): yield x @@ -725,7 +724,7 @@ def rft_type_rules(self): """ if self._rft_type_rules is None: vartypes = {} - lines = self.opt('var_types') + lines = self.rft_opt('var_types') for line in lines.splitlines() if lines else []: varinfo, tname = line.split() if varinfo.endswith('}'): @@ -754,7 +753,7 @@ def used_external_types(self): """Used external types""" if self._used_external_types is None: used_types = {} - names = self.opt('rft_external_types') + names = self.rft_opt('external_types') if names is None: names = [ 'builtins', @@ -776,7 +775,7 @@ def rft_external_types(self): """External types manual""" value = self.rft_options.get('external_types', '') if value: - for x in value: + for x in value.split(): yield x @property @@ -787,7 +786,7 @@ def rft_external_attrs(self): """ value = self.rft_options.get('external_attrs', '') if value: - for x in value: + for x in value.split(): yield x @property From 978d3e15d6f0b7a589c9250ba0a4d5741d6a3229 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 23 Feb 2025 09:43:35 +0800 Subject: [PATCH 159/337] Log unknown calls --- src/cli/project.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/cli/project.py b/src/cli/project.py index 222c1285..33b62534 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -341,6 +341,7 @@ class Project: """ ATTR_LOGFILE = '.pyarmor/project/rft_unknown_attrs.log' + CALL_LOGFILE = '.pyarmor/project/rft_unknown_calls.log' def __init__(self, ctx): self.ctx = ctx @@ -427,7 +428,7 @@ def __init__(self, ctx): # # self.unknown_calls.append("foo:fa:c.runner.echo*") # - self.unknown_calls = [] + self.unknown_calls = None # Log all attributes which is external base class attr # @@ -755,11 +756,8 @@ def used_external_types(self): used_types = {} names = self.rft_opt('external_types') if names is None: - names = [ - 'builtins', - '.pyarmor/project/rft_external_types.json' - ] - for name in names: + names = 'builtins .pyarmor/project/rft_external_types.json' + for name in names.split(): if name.endswith('.json'): if exists(name): with open(name) as f: @@ -911,9 +909,11 @@ def vlist(name): def start(self): self._logfile = open(self.ATTR_LOGFILE, 'w') + self._logfile2 = open(self.CALL_LOGFILE, 'w') def stop(self): self._logfile.close() + self._logfile2.close() def log_unknown_attr(self, line): fields = line.split(':') @@ -928,9 +928,9 @@ def log_unknown_func(self, func): if func not in self.unknown_funcs: self.unknown_funcs.append(func) - def log_unknown_call(self, func): - if func not in self.unknown_calls: - self.unknown_calls.append(func) + def log_unknown_call(self, line): + self.unknown_calls = True + self._logfile2.write(line + '\n') def _get_external_types(self, modname, pypaths=None): from sys import executable From 9b1d6a22c7369c5e3b4a0d0a0d6dfe0141525d0d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 23 Feb 2025 09:43:43 +0800 Subject: [PATCH 160/337] Fix bug --- src/cli/command.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/command.py b/src/cli/command.py index 9403bf3e..65f5731a 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -406,7 +406,8 @@ def cmd_build(self, ctx, args): def _build(self, project, target, output, value=None): from .core import Pytransform3 args = [self.ctx, target, project, output, value] - Pytransform3.pre_build(args) + m = Pytransform3.init(self.ctx) + m.pre_build(args) def cmd_env(self, ctx, args): """Check and change pyarmor settings From 3443f02c5c83b24eacd80889aecbcc7af2f07bf1 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 23 Feb 2025 10:47:24 +0800 Subject: [PATCH 161/337] Add destpath for module --- src/cli/project.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cli/project.py b/src/cli/project.py index 33b62534..1a4baeee 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -185,6 +185,11 @@ def abspath(self): return (self._path if isabs(self._path) else joinpath(self.parent.abspath, self._path)) + @property + def destpath(self): + s = self.qualname + ('' if self._name else '.__init__') + return joinpath(*s.split('.')) + splitext(self.path)[-1] + def compile_file(self, force=False): if self._co is not None and not force: return From bfbb67595d19ffc48f95e7dbc437bd9b122a8e6e Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 23 Feb 2025 22:20:26 +0800 Subject: [PATCH 162/337] Refine code --- src/cli/project.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cli/project.py b/src/cli/project.py index 1a4baeee..09618799 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -759,17 +759,17 @@ def used_external_types(self): """Used external types""" if self._used_external_types is None: used_types = {} - names = self.rft_opt('external_types') - if names is None: - names = 'builtins .pyarmor/project/rft_external_types.json' + names = 'builtins' for name in names.split(): if name.endswith('.json'): if exists(name): with open(name) as f: used_types.update(jsonload(f)) else: - m = name.split('.')[0] - used_types.update(self._get_external_types(m)) + modname = name.split(':')[0] + alltypes = self._get_external_types(modname) + if alltypes: + used_types.update(alltypes) self._used_external_types = used_types return self._used_external_types From 5da762c4a13dfe1330e17b4558e87d8540c611e3 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 24 Feb 2025 17:26:20 +0800 Subject: [PATCH 163/337] Add testcases --- tests.8/accept_test.py | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests.8/accept_test.py b/tests.8/accept_test.py index 344c4ef5..d10c13fd 100755 --- a/tests.8/accept_test.py +++ b/tests.8/accept_test.py @@ -38,6 +38,16 @@ def wrap(self, *args, **kwargs): return wrap +def only_v9test(func): + + def wrap(self, *args, **kwargs): + if self.is_trial() or sys.version_info[1] < 9: + self.skipTest('v9 pro case') + func(self, *args, **kwargs) + + return wrap + + class BaseTestCase(unittest.TestCase): def setUp(self): @@ -48,6 +58,13 @@ def setUp(self): def is_trial(self): return not os.path.exists(os.path.join(self.home, 'license.lic')) + def copy_mini_extension(self): + root, ext = (('C:/', '.pyd') if sys.platform == 'windows' + else (os.path.expanduser('~'), '.so')) + tag = f'cp3.{sys.version_info[1]}' + plat = 'darwin.x86_64' + shutil.copy(f'{root}/workspace/pytransform/slight/extensions/{plat}/libs/{tag}/pyarmor_mini{ext}', '.') + def tearDown(self): shutil.rmtree(self.local_path, ignore_errors=True) shutil.rmtree(self.default_output, ignore_errors=True) @@ -385,6 +402,34 @@ def test_thread_with_bcc(self): 'hello bob', ]])) + @only_v9test + def test_v9_rft_foo(self): + args = ['init', '-e', 'samples/foo.py'] + self.pyarmor_cmd(args) + args = ['build', '--rft'] + self.pyarmor_cmd(args) + self.verify_dist_foo() + + @only_v9test + def test_v9_mini_foo(self): + self.copy_mini_extension() + args = ['init', '-e', 'samples/foo.py'] + self.pyarmor_cmd(args) + args = ['build', '--mini'] + self.pyarmor_cmd(args) + shutil.copy('pyarmor_mini.so', 'dist') + self.verify_dist_foo() + + @only_v9test + def test_v9_mini_rft_foo(self): + self.copy_mini_extension() + args = ['init', '-e', 'samples/foo.py'] + self.pyarmor_cmd(args) + args = ['build', '--mini-rft'] + self.pyarmor_cmd(args) + shutil.copy('pyarmor_mini.so', 'dist') + self.verify_dist_foo() + if __name__ == '__main__': logging.getLogger().addHandler(logging.NullHandler()) From 56c5a56947875be2dffb761050be69281f9fb987 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 25 Feb 2025 07:10:24 +0800 Subject: [PATCH 164/337] Change pyarmor.cli.core version to 7.6.5 --- src/cli/core/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index 427738b9..89dcc68a 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -20,7 +20,7 @@ # @Create Date: Thu Jan 12 17:29:25 CST 2023 # -__VERSION__ = '7.6.4' +__VERSION__ = '7.6.5' PLATFORM_NAMES = ( 'windows.x86_64', 'windows.x86', 'cygwin.x86_64', From 2a50259fb4c484093f50cc8d112712e6928cdd89 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 25 Feb 2025 21:52:08 +0800 Subject: [PATCH 165/337] Change version to 9.1.1 --- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/__init__.py b/src/cli/__init__.py index e788167e..9a8ab90c 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.1.0' +__VERSION__ = '9.1.1' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index de15a40a..e69816ee 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 1 -patch = 0 +patch = 1 ;; Compatible core version cli.core = 7.6.4 diff --git a/src/config.py b/src/config.py index 596befd0..bc0b8504 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.1.0' +version = '9.1.1' # The corresponding version of pytransform.so core_version = 'r52.6' From a92dce46d21db0d02eb3502d414bbd8be3a1d57e Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 26 Feb 2025 08:02:06 +0800 Subject: [PATCH 166/337] Fix broken link --- README.md | 2 +- docs/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 528a2047..0d578f6e 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Please read the [Pyarmor EULA](LICENSE). ## Getting Help -- **[Ask in learning system][askeke] or [look through check list][checklist]** +- **[Look through check list][checklist]** - **Consult the [Pyarmor Documentation][doc].** - **Check the [FAQ][faq] for answers to common questions.** - **Try the documentation [index][genindex] or the [detailed table of contents][mastertoc].** diff --git a/docs/index.rst b/docs/index.rst index 7622d0ed..b7d3413a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,7 +27,7 @@ How the documentation is organized New features introduced by Pyarmor 9.1.0 are moved to Pyarmor Learning System - https:://eke.dashingsoft.com/pyarmor/docs/en/index.html + https://eke.dashingsoft.com/pyarmor/docs/en/index.html It includes 3 commands: `pyarmor init`, `pyarmor env`, `pyarmor build` From ad1b30f160ece432a666bf583a59ec87200e2dd9 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 26 Feb 2025 10:34:13 +0800 Subject: [PATCH 167/337] Add link targets --- docs/reference/solutions.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/reference/solutions.rst b/docs/reference/solutions.rst index aaaf8890..74d4018d 100644 --- a/docs/reference/solutions.rst +++ b/docs/reference/solutions.rst @@ -13,6 +13,8 @@ Build Device If something is wrong in build device, check this section. +.. _fix-bootstrap-issue: + Bootstrap failed ---------------- @@ -90,6 +92,8 @@ Bootstrap failed 4. If this platform is supported, try to upgrade Python interpreter to latest patch version. For example, upgrade Python 3.11.0b2 to 3.11.9 +.. _fix-register-issue: + Registration Failed ------------------- @@ -147,6 +151,8 @@ If it returns as above, but still failed to register, report issue with license 1. Check docker host and container network, make sure they're in same network +.. _fix-obfuscate-issue: + Obfuscation failed ------------------ @@ -175,6 +181,8 @@ Please check above section `Registration Failed` - A script as simple as possible to reproduce issue - Do not use the options which doesn't make sense for this issue +.. _fix-pack-issue: + Packing failed -------------- @@ -188,6 +196,8 @@ Target Device If your own code in the obfuscated script still isn't executed, check `Bootstrap failed`, otherwise check `failed to run obfuscated scripts` or `failed to run the packed obfuscated scripts` +.. _fix-runtime-bootstrap-issue: + Bootstrap failed ---------------- @@ -244,6 +254,8 @@ If still not work, please check :doc:`environments` to make sure Pyarmor support 2. Use `pyarmor cfg assert.call:excludes "xxx"` and `pyarmor cfg assert.import:excludes "xxx"` to exclude problem modules and functions 3. Find the problem option, and report issue +.. _fix-run-obfuscated-script-issue: + Failed to run obfuscated scripts -------------------------------- @@ -269,6 +281,8 @@ First use a hello world script to make sure it works. If it doesn't work, check Check solutions in :ref:`using bccmode` +.. _fix-run-packed-script-issue: + Failed to run the packed obfuscated scripts ------------------------------------------- From d0015fa14dedc8ffe20e2722d68e56ec3356db9f Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 1 Mar 2025 10:48:05 +0800 Subject: [PATCH 168/337] Add links --- README.md | 2 +- docs/tutorial/advanced.rst | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d578f6e..5a883a37 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Please read the [Pyarmor EULA](LICENSE). ## Getting Help -- **[Look through check list][checklist]** +-- **[Ask in learning system][askeke] or [look through check list][checklist]** - **Consult the [Pyarmor Documentation][doc].** - **Check the [FAQ][faq] for answers to common questions.** - **Try the documentation [index][genindex] or the [detailed table of contents][mastertoc].** diff --git a/docs/tutorial/advanced.rst b/docs/tutorial/advanced.rst index a829e6c1..9d59b8c0 100644 --- a/docs/tutorial/advanced.rst +++ b/docs/tutorial/advanced.rst @@ -388,6 +388,8 @@ When obfuscated scripts start, it checks :envvar:`LANG` to get current language export PYARMOR_LANG=zh_TW python dist/foo.py +.. _generating cross platform scripts: + Generating cross platform scripts ================================= @@ -409,6 +411,8 @@ Using :option:`--platform` multiple times to support multiple platforms. For exa --platform darwin.x86_64 \ foo.py +.. _support-multiple-python-versions: + Obfuscating scripts for multiple Python versions ================================================ From 691c1306c8402dfc3859d815aea502d5d780ac00 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 4 Mar 2025 06:26:57 +0800 Subject: [PATCH 169/337] Doc how to fix Github Action issue --- docs/how-to/ci.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 203a5f85..96c1b30f 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -37,6 +37,10 @@ Notes * Once CI regfile doesn't work, require new one * One license can request <= 100 CI regfiles +.. note:: + + In GitHub Action, it may be one extra step, refer to this thread `Error when using CI license in CI pipeline `_ + .. important:: In CI/CD pipeline, each run will send license and docker information to Pyarmor License Server, excessive requests or requests beyond normal usage may be rejected by Pyarmor License Server From 3400fc8c5a3da06f9d39d8604a5b78c028d589b1 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 4 Mar 2025 06:28:32 +0800 Subject: [PATCH 170/337] Rename option `auto_export_mode` to `export_mode` --- src/cli/model.py | 2 +- src/cli/project.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/model.py b/src/cli/model.py index b6b7df4c..45a2bad9 100644 --- a/src/cli/model.py +++ b/src/cli/model.py @@ -323,7 +323,7 @@ class RftAttrFilter(ListOption): value='3', ), OptionModel( - name='auto_export_mode', + name='export_mode', cls='BoolOption', value=0, ), diff --git a/src/cli/project.py b/src/cli/project.py index 09618799..7ea0a476 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -533,7 +533,7 @@ def rft_options(self): 1, reform string by rft_string_filters 2, reform all string - - auto_export_mode: bool + - export_mode: bool True: auto export all names in module.__all__ From 93c7da88d363cc523e1c4e5f751a39bcee438a6d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 4 Mar 2025 06:28:52 +0800 Subject: [PATCH 171/337] Change option `recursive` for `pyarmor init` --- src/cli/command.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index 65f5731a..283fc886 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -65,12 +65,12 @@ def init_parser(self, subparsers): parser.add_argument( '-x', '--exclude', metavar='PATTERN', dest='excludes', action='append', - help='exclude file or path from src' + help='exclude file or path' ) parser.add_argument( - '-r', '--recursive', action='store_true', - help='search modules and packages recursively' + '-r', '--recursive', choices=(0, 1, 2, 3), + help='how to search modules and packages' ) parser.add_argument( '-C', '--clean', action='store_true', From 7f7aa4906ca6675e73329855c0736ccd91020ace Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 4 Mar 2025 07:10:35 +0800 Subject: [PATCH 172/337] Refine doc --- docs/how-to/ci.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 96c1b30f..059f4ee9 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -39,7 +39,17 @@ Notes .. note:: - In GitHub Action, it may be one extra step, refer to this thread `Error when using CI license in CI pipeline `_ + In GitHub Action, it need one extra step, otherwise `CI license only works in CI/CD pipeline` + + 1. For Ubuntu, add this step:: + + - run: sudo mv /dev/disk /dev/disk-none + + 2. For Darwin, add this step:: + + - run: sudo mv /dev/rdisk0 /dev/rdisk0-none + + Refer to this thread `Error when using CI license in CI pipeline `_ .. important:: From 57eb903748bcce5d032a094f0bc7ec3232895a83 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 5 Mar 2025 21:40:04 +0800 Subject: [PATCH 173/337] Refine options --- src/cli/command.py | 19 ++++- src/cli/model.py | 13 ++- src/cli/project.py | 195 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 212 insertions(+), 15 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index 283fc886..4a65a25f 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -23,7 +23,7 @@ import configparser import shlex -from os import makedirs +from os import makedirs, remove as rmfile from os.path import abspath, exists, join as joinpath, relpath from . import logger, CliError @@ -69,8 +69,8 @@ def init_parser(self, subparsers): ) parser.add_argument( - '-r', '--recursive', choices=(0, 1, 2, 3), - help='how to search modules and packages' + '-r', '--recursive', action='store_true', + help='Search modules and packages recursively' ) parser.add_argument( '-C', '--clean', action='store_true', @@ -263,7 +263,17 @@ def cmd_init(self, ctx, args): if args.clean: logger.info('clean old project') if cfg.has_section(sectname): + logger.debug('remove cfg section: project') cfg.remove_section(sectname) + if cfg.has_section('rft'): + logger.debug('remove cfg section: rft') + cfg.remove_section('rft') + propath = joinpath(ctx.local_path, 'project') + for name in ['rft_autofix.rules']: + profile = joinpath(propath, name) + if exists(profile): + logger.debug('remove file: %s', profile) + rmfile(profile) if cfg.has_section(sectname): logger.info('change project settings') @@ -309,7 +319,7 @@ def format_path(plist, raw=False): data['packages'] = format_path(args.packages) if args.recursive: - data['recursive'] = 1 + data['recursive'] = '1' for key, value in data.items(): old = sect.get(key) @@ -391,6 +401,7 @@ def cmd_build(self, ctx, args): output = args.output if args.output else 'dist' self._build(project, 'autofix', output, value) logger.info('build auto-fix-table:%s end', value) + project.preview_autofix_result(value) elif args.randname is not None: value = args.randname logger.info('build rand-pool:%s ...', value) diff --git a/src/cli/model.py b/src/cli/model.py index 45a2bad9..7c96db4b 100644 --- a/src/cli/model.py +++ b/src/cli/model.py @@ -299,6 +299,11 @@ class RftAttrFilter(ListOption): cls='BoolOption', value=0, ), + OptionModel( + name='pypaths', + cls='PatternOption', + many=True, + ), ], 'rft': [ @@ -332,10 +337,6 @@ class RftAttrFilter(ListOption): cls='NameOption', many=True, ), - OptionModel( - name='var_types', - cls='DictOption', - ), OptionModel( name='exclude_names', cls='RftNamePattern', @@ -364,6 +365,10 @@ class RftAttrFilter(ListOption): name='call_rules', cls='RftAttrFilter', ), + OptionModel( + name='var_types', + cls='DictOption', + ), ], 'rft_filter': [ OptionModel( diff --git a/src/cli/project.py b/src/cli/project.py index 7ea0a476..87924612 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -43,14 +43,14 @@ packages = pattern path@name @section ... excludes = patterh patterh ... -rpath = path path +pypaths = path modname::path Examples -------- -1. Create a project in current path +1. Create a project with scripts/packages in current path - $ pyarmor init + $ pyarmor init -r 2. Obfuscate all the scripts in the project @@ -766,7 +766,7 @@ def used_external_types(self): with open(name) as f: used_types.update(jsonload(f)) else: - modname = name.split(':')[0] + modname = name.split('::')[0] alltypes = self._get_external_types(modname) if alltypes: used_types.update(alltypes) @@ -876,8 +876,7 @@ def vlist(name): # 3 forms: path, path@name, @sect i = item.find('@') if i == 0: - logger.info('package at section %s', item) - continue + raise NotImplementedError(f'package {item}') if i == -1: path, pkgname = item, basename(item) else: @@ -887,7 +886,8 @@ def vlist(name): obj = Package(path, name=pkgname, parent=self) self._packages.append(obj) - elif not modules and not scripts: + recursive = data.get('recursive', '0') + if recursive == '1': pkginit = joinpath(src, '__init__.py') if exists(pkginit): pkgname = name if name else basename(src) @@ -975,6 +975,187 @@ def get_external_type(self, modname, clsname=None): return extypes return extypes.get('%s.%s' % (modname, clsname), []) + def preview_autofix_result(self, mode): + if mode not in (2, 3): + return + output = f'.pyarmor/project/rft_autofix.{mode}.org' + + rulefile = '.pyarmor/project/rft_autofix.rules' + if not exists(rulefile): + logger.info('no found %s', rulefile) + return + with open(rulefile) as f: + fixtable = jsonload(f) + + if mode == 2: + self._preview_autofix_2(fixtable, output) + elif mode == 3: + self._preview_autofix_3(fixtable, output) + + def _preview_autofix_2(self, fixtable, output): + modules = fixtable['modules'] + attrinfo = {} + with open(self.ATTR_LOGFILE) as f: + # Line format: + # modname:scopes:attrs:index:begin_line,end_line + for line in f: + line = line.strip() + fields = line.split(':') + modname, scope = fields[:2] + attrs = [x for x in fields[2].split('.') + if x[:1] not in ('(', '[')] + start = int(fields[3]) + lineno = [int(x) for x in fields[4].split(',')] + for s in attrs[start:]: + attrinfo.setdefault(s, []) + fname = modules.get(modname, modname) + title = ':'.join([ + modname, scope, '.'.join(attrs) + ]) + attrinfo[s].append([fname, lineno[0], title]) + + header = Template(dedent("""\ + * RFT AutoFix Mode 2 + + The following attributes have been renamed, if any of + them should not be renamed, configure it as external + attribute and run autofix mode again. + + For example, suppose `append` is external attribute: + + pyarmor env -p push rft:external_attrs append + pyarmor build --autofix 2 + pyarmor build --rft + + Refactoring Attribute List + + $attrs + + """)) + attrsect = Template(dedent("""\ + ** $name + + $srclinks + + """)) + + inattrs = fixtable.get('include_attrs', []) + rftattrs = header.substitute(attrs='\n'.join( + [f'[[*{x}][{x}]]' for x in inattrs])) + with open(output, 'w') as fp: + fp.write(rftattrs) + for attr in inattrs: + infos = attrinfo.get(attr) + if infos is None: + continue + fp.write(attrsect.substitute( + name=attr, + srclinks='\n'.join([ + f'- [[file:{fname}::{n}][{title}]]' + for fname, n, title in infos + ]) + )) + + def _preview_autofix_3(self, fixtable, output): + modules = fixtable['modules'] + confused_names = fixtable.get('confused_names', []) + attrules = {} + rftattrs = {} + with open(self.ATTR_LOGFILE) as f: + # Line format: + # modname:scopes:attrs:index:begin_line,end_line + for line in f: + fields = line.strip().split(':') + modname, scope = fields[:2] + attrs = [x for x in fields[2].split('.') + if x[:1] not in ('(', '[')] + start = int(fields[3]) + lineno = fields[4].split(',')[0] + actions = ['*'] * start + rnames = [] + for s in attrs[start:]: + if s in confused_names: + actions.append('?') + rnames.append(s) + else: + actions.append('*') + if rnames: + aname = '.'.join(attrs) + pat = '.'.join(actions) + filename = modules.get(modname, modname) + rule = f'{modname}::{scope}:{aname} {pat}' + anchor = modname, filename, lineno + if rule not in attrules: + attrules[rule] = [anchor] + else: + attrules[rule].append(anchor) + s = rnames[0] + rftattrs.setdefault(s, set()) + rftattrs[s].add(rule) + + header = Template(dedent("""\ + * RFT AutoFix Mode 3 + + The following attributes have been renamed, if any of + them should not be renamed, add one attribute rule and + run autofix mode again. + + For example, there is one line + + *** rftbuild::log_patch_script:cfgpatch.append *.? + - rftbuild.py::2690 + + But `cfgpatch` is builtin type `list`, and its attribute + `append` shouldn't be renamed + + In this case, replace `?` with `*`, add one rule: + + pyarmor env -p push rft:attr_rules "rftbuild::log_patch_script:cfgpatch.append *.*" + + Then rebuild the project: + + pyarmor build --autofix 3 + pyarmor build --rft + + Refactoring Attribute List + + $attrs + + """)) + attrsect = Template(dedent("""\ + ** $name + + $rules + + """)) + + rulesect = Template(dedent("""\ + *** $rule + $srclinks + """)) + + attrs = sorted(rftattrs.keys()) + with open(output, 'w') as fp: + fp.write(header.substitute( + attrs='\n'.join([f'[[*{x}][{x}]]' for x in attrs]) + )) + for key in attrs: + rules = rftattrs[key] + rlist = [] + for r in rules: + infos = attrules[r] + rlist.append(rulesect.substitute( + rule=r, + srclinks='\n'.join([ + f'- [[file:{s}::{n}][{m}:{n}]]' + for m, s, n in infos + ]) + )) + fp.write(attrsect.substitute( + name=key, + rules='\n'.join(rlist) + )) + def _as_dot(self): """Map project to dot graph""" modules = [x._as_dot() for x in self.modules] From 5d2bfb2167e4897f4223a051fc22382aa5c54ad6 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 6 Mar 2025 08:51:38 +0800 Subject: [PATCH 174/337] Refine testcases for v9.1 features --- tests.8/Makefile | 1 + tests.8/accept_test.py | 15 +++++---------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/tests.8/Makefile b/tests.8/Makefile index 2d146a7f..4f83c30a 100644 --- a/tests.8/Makefile +++ b/tests.8/Makefile @@ -103,6 +103,7 @@ test-%: install-%: @rm -rf ./pyarmor ./pyarmor.cli.core.themida* @tar xzf ${pkgpath}/pyarmor-${testver}.tar.gz + @cp ${pkgpath}/mini.extensions/${HOST}/libs/cp${PYVER}/pyarmor_mini.* . @mv pyarmor-${testver}/pyarmor . @rm -rf pyarmor-${testver} @unzip ${whlpath}/pyarmor.cli.core-${corever}-cp$(subst .,,$(subst install-,,$@))-none-${platform}.whl > /dev/null diff --git a/tests.8/accept_test.py b/tests.8/accept_test.py index d10c13fd..efd9cd6f 100755 --- a/tests.8/accept_test.py +++ b/tests.8/accept_test.py @@ -58,12 +58,9 @@ def setUp(self): def is_trial(self): return not os.path.exists(os.path.join(self.home, 'license.lic')) - def copy_mini_extension(self): - root, ext = (('C:/', '.pyd') if sys.platform == 'windows' - else (os.path.expanduser('~'), '.so')) - tag = f'cp3.{sys.version_info[1]}' - plat = 'darwin.x86_64' - shutil.copy(f'{root}/workspace/pytransform/slight/extensions/{plat}/libs/{tag}/pyarmor_mini{ext}', '.') + def copy_mini_extension(self, output='dist'): + ext = '.pyd' if sys.platform == 'windows' else '.so' + shutil.copy(f'pyarmor_mini{ext}', output) def tearDown(self): shutil.rmtree(self.local_path, ignore_errors=True) @@ -412,22 +409,20 @@ def test_v9_rft_foo(self): @only_v9test def test_v9_mini_foo(self): - self.copy_mini_extension() args = ['init', '-e', 'samples/foo.py'] self.pyarmor_cmd(args) args = ['build', '--mini'] self.pyarmor_cmd(args) - shutil.copy('pyarmor_mini.so', 'dist') + self.copy_mini_extension() self.verify_dist_foo() @only_v9test def test_v9_mini_rft_foo(self): - self.copy_mini_extension() args = ['init', '-e', 'samples/foo.py'] self.pyarmor_cmd(args) args = ['build', '--mini-rft'] self.pyarmor_cmd(args) - shutil.copy('pyarmor_mini.so', 'dist') + self.copy_mini_extension() self.verify_dist_foo() From 777c809b1f479fde25f2dcab567f4d213f8ebbb6 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 6 Mar 2025 12:56:26 +0800 Subject: [PATCH 175/337] Change version to 9.1.2 --- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 9a8ab90c..9e981aa6 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.1.1' +__VERSION__ = '9.1.2' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index e69816ee..6b893cc0 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 1 -patch = 1 +patch = 2 ;; Compatible core version cli.core = 7.6.4 diff --git a/src/config.py b/src/config.py index bc0b8504..9adaf625 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.1.1' +version = '9.1.2' # The corresponding version of pytransform.so core_version = 'r52.6' From 5f4894bb2d7d73a4e24265bec7ae3802fce5754a Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 6 Mar 2025 12:57:06 +0800 Subject: [PATCH 176/337] Fix testcase issue --- tests.8/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests.8/Makefile b/tests.8/Makefile index 4f83c30a..81ff35c2 100644 --- a/tests.8/Makefile +++ b/tests.8/Makefile @@ -103,7 +103,7 @@ test-%: install-%: @rm -rf ./pyarmor ./pyarmor.cli.core.themida* @tar xzf ${pkgpath}/pyarmor-${testver}.tar.gz - @cp ${pkgpath}/mini.extensions/${HOST}/libs/cp${PYVER}/pyarmor_mini.* . + @test -d ${pkgpath}/mini.extensions/${HOST} && cp ${pkgpath}/mini.extensions/${HOST}/libs/cp${PYVER}/pyarmor_mini.* . @mv pyarmor-${testver}/pyarmor . @rm -rf pyarmor-${testver} @unzip ${whlpath}/pyarmor.cli.core-${corever}-cp$(subst .,,$(subst install-,,$@))-none-${platform}.whl > /dev/null From 86baa0aee12550fe169177fb5d757466f834528b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 14 Mar 2025 10:41:07 +0800 Subject: [PATCH 177/337] Add idea for checking old license --- docs/questions.rst | 4 ++++ docs/tutorial/advanced.rst | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/questions.rst b/docs/questions.rst index 7e639755..ed51fb32 100644 --- a/docs/questions.rst +++ b/docs/questions.rst @@ -371,6 +371,10 @@ Upgrading There is no identical pack in Pyarmor 8, Pyarmor 8+ only provide repack function to handle bundle of PyInstaller. Refer to basic tutorial, topic `insight into pack`__ and this solved issue `Pyarmor pack missing in pyarmor 8.0`__ +**Using PyArmor 9.x newer version, we cannot generated licenses compatible with "Software" created using older PyArmor (7.x versions), mainly using license.lic. Correct?** + + You're right. At this time one possible solution is still using Pyarmor 7 obfuscated script to verify old runtime key, in Pyarmor 9 obfuscated script check old runtime key by calling Pyarmor 7 obfuscated script indirectly (IPC) + __ https://pyarmor.readthedocs.io/en/stable/topic/repack.html __ https://github.com/dashingsoft/pyarmor/discussions/1107 diff --git a/docs/tutorial/advanced.rst b/docs/tutorial/advanced.rst index 9d59b8c0..a765717e 100644 --- a/docs/tutorial/advanced.rst +++ b/docs/tutorial/advanced.rst @@ -480,4 +480,13 @@ If using :term:`outer key` with runtime package, it need specify `--outer` both $ pyarmor gen key -e .10 $ mv dist/pyarmor.rkey dist/pyarmor_runtime_000000 +.. _check-pyarmor-7-license: + +Working with old runtime key +============================ + +If still need check Pyarmor 7 runtime key in the obfuscated scripts of Pyarmor 9, here it's one possible solution + +The idea is still using Pyarmor 7 obfuscated script to verify old runtime key, in Pyarmor 9 obfuscated script check old runtime key by calling Pyarmor 7 obfuscated script indirectly (IPC) + .. include:: ../_common_definitions.txt From ecdc7c59866441cfeb7f6f06d651160f2b58c093 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 19 Mar 2025 08:25:56 +0800 Subject: [PATCH 178/337] change core version to 7.6.5 --- setup.py | 2 +- src/cli/default.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f5f6634a..9224bcd7 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,7 @@ }, install_requires=[ - 'pyarmor.cli.core~=7.6.4' + 'pyarmor.cli.core~=7.6.5' ], entry_points={ diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 6b893cc0..fb16d2f3 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -6,7 +6,7 @@ minor = 1 patch = 2 ;; Compatible core version -cli.core = 7.6.4 +cli.core = 7.6.5 mini.core = 1.0 ecc.core = 1.0 vmc.core = 1.0 From 0616fd5c297b7f658a6a24cba0786cf3d2a6bfd9 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 19 Mar 2025 11:54:26 +0800 Subject: [PATCH 179/337] Fix format error --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a883a37..528a2047 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Please read the [Pyarmor EULA](LICENSE). ## Getting Help --- **[Ask in learning system][askeke] or [look through check list][checklist]** +- **[Ask in learning system][askeke] or [look through check list][checklist]** - **Consult the [Pyarmor Documentation][doc].** - **Check the [FAQ][faq] for answers to common questions.** - **Try the documentation [index][genindex] or the [detailed table of contents][mastertoc].** From b7410f6233ae30a1e689802c388237357094e972 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 19 Mar 2025 19:49:13 +0800 Subject: [PATCH 180/337] Fix bug --- src/cli/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/project.py b/src/cli/project.py index 87924612..1fcb9bbd 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -187,7 +187,7 @@ def abspath(self): @property def destpath(self): - s = self.qualname + ('' if self._name else '.__init__') + s = self.qualname + ('' if self.name else '.__init__') return joinpath(*s.split('.')) + splitext(self.path)[-1] def compile_file(self, force=False): From e7df428fc973a57e96f9e34bc382e86e4f21e6b0 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 20 Mar 2025 09:09:09 +0800 Subject: [PATCH 181/337] Enable project filter for packages --- src/cli/project.py | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/src/cli/project.py b/src/cli/project.py index 1fcb9bbd..56c4813a 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -241,17 +241,34 @@ def rpaths(self): class Package(Module): """Package concept""" - def __init__(self, path, name=None, parent=None): + def __init__(self, path, name=None, parent=None, excludes=[]): super().__init__(path, name=name, parent=parent) self._modules = None self._packages = None + self._excludes = excludes if excludes else GLOBAL_EXCLS + self._filters = None + + @property + def filters(self): + if self._filters is None: + self._filters = [] + for x in self._excludes: + i = x.find(':') + if i == -1: + self._filters.append(x) + elif fnmatch(self.qualname, x[:i]): + self._filters.append(x[i+1:]) + return self._filters def load(self): - excludes = GLOBAL_EXCLS - files, dirs = scan_path(self.abspath, excludes=excludes) + excls = self.filters + files, dirs = scan_path(self.abspath, excludes=excls) self._modules = [Module(x, parent=self) for x in files] - self._packages = [Package(x, parent=self) for x in dirs] + self._packages = [ + Package(x, parent=self, excludes=self._excludes) + for x in dirs + ] @property def modules(self): @@ -858,17 +875,19 @@ def vlist(name): src = self.src = data['src'] name = data.get('name') excludes = vlist('excludes') + list(GLOBAL_EXCLS) + proexcls = [x.strip(':') for x in excludes + if x.find(':') < 1] scripts = [] for pat in vlist('scripts'): - scripts.extend(search_item(src, pat, excludes)) + scripts.extend(search_item(src, pat, proexcls)) self._scripts.extend([ Script(self.relsrc(x), parent=self) for x in scripts ]) modules = [] for pat in vlist('modules'): - modules.extend(search_item(src, pat, excludes)) + modules.extend(search_item(src, pat, proexcls)) packages = vlist('packages') if packages: @@ -883,7 +902,10 @@ def vlist(name): path, pkgname = item.split('@') if not isabs(path): path = joinpath(src, path) - obj = Package(path, name=pkgname, parent=self) + obj = Package(path, + name=pkgname, + parent=self, + excludes=excludes) self._packages.append(obj) recursive = data.get('recursive', '0') @@ -891,10 +913,13 @@ def vlist(name): pkginit = joinpath(src, '__init__.py') if exists(pkginit): pkgname = name if name else basename(src) - obj = Package(src, name=pkgname, parent=self) + obj = Package(src, + name=pkgname, + parent=self, + excludes=excludes) self._packages.append(obj) else: - files, dirs = scan_path(src, excludes=excludes) + files, dirs = scan_path(src, excludes=proexcls) modules.extend([joinpath(src, x) for x in files]) self._packages.extend([ Package(x, parent=self) for x in dirs From fc109fb2cf58f40360281dee6e1d3bed68edc774 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 20 Mar 2025 21:00:06 +0800 Subject: [PATCH 182/337] Hide errors when importing invalid external types --- src/cli/project.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/cli/project.py b/src/cli/project.py index 56c4813a..b982d7f5 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -964,7 +964,7 @@ def log_unknown_call(self, line): def _get_external_types(self, modname, pypaths=None): from sys import executable - from subprocess import check_output, CalledProcessError + from subprocess import check_output, CalledProcessError, DEVNULL source = Template(dedent("""\ import json import sys @@ -985,12 +985,16 @@ def _get_external_types(self, modname, pypaths=None): """)).substitute(name=modname, pypaths=repr(pypaths)) try: - output = check_output([executable, '-c', source]) + output = check_output([executable, '-c', source], stderr=DEVNULL) return jsonloads(output) except CalledProcessError: pass def get_external_type(self, modname, clsname=None): + if not all([x.isidentifier() for x in modname.split('.')]): + logger.debug('invalid external module: %s', modname) + return [] + extypes = self.used_external_types if modname not in extypes: result = self._get_external_types(modname) From 40a59093b74e5404202da015604781d1006ecc75 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 20 Mar 2025 21:43:56 +0800 Subject: [PATCH 183/337] Change package name format with PEP --- tests.8/Makefile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests.8/Makefile b/tests.8/Makefile index 81ff35c2..e51aa08a 100644 --- a/tests.8/Makefile +++ b/tests.8/Makefile @@ -81,7 +81,7 @@ whlpath := $(shell (test -d ${whlpath} && echo ${whlpath}) || echo ./pkgdist) pkgfile := $(word 1,$(shell cd ${pkgpath}; ls -t pyarmor-*.tar.gz)) testver := $(subst pyarmor-,,$(subst .tar.gz,,${pkgfile})) -coreprefix := pyarmor.cli.core- +coreprefix := pyarmor_cli_core- coresuffix := -cp38-none-macosx_10_14_x86_64.whl corever=$(subst ${coreprefix},,$(subst ${coresuffix},,$(shell cd ${whlpath}; ls -t ${coreprefix}*${coresuffix}))) @@ -101,14 +101,14 @@ test-%: PYVER=$(subst test-,,$@) make install-$(subst test-,,$@) atest ptest install-%: - @rm -rf ./pyarmor ./pyarmor.cli.core.themida* + @rm -rf ./pyarmor ./pyarmor_cli_core_themida* @tar xzf ${pkgpath}/pyarmor-${testver}.tar.gz - @test -d ${pkgpath}/mini.extensions/${HOST} && cp ${pkgpath}/mini.extensions/${HOST}/libs/cp${PYVER}/pyarmor_mini.* . + -@test -d ${pkgpath}/mini.extensions/${HOST} && cp ${pkgpath}/mini.extensions/${HOST}/libs/cp${PYVER}/pyarmor_mini.* . @mv pyarmor-${testver}/pyarmor . @rm -rf pyarmor-${testver} - @unzip ${whlpath}/pyarmor.cli.core-${corever}-cp$(subst .,,$(subst install-,,$@))-none-${platform}.whl > /dev/null - @(themida=../pyarmor.cli.core.themida-${corever}-cp$(subst .,,$(subst install-,,$@))-none-any.whl && test -f $$themida && unzip $$themida > /dev/null || echo no themida $$themida) - @rm -rf pyarmor.cli.core-${corever}.dist-info + @unzip ${whlpath}/pyarmor_cli_core-${corever}-cp$(subst .,,$(subst install-,,$@))-none-${platform}.whl > /dev/null + @(themida=../pyarmor_cli_core_themida-${corever}-cp$(subst .,,$(subst install-,,$@))-none-any.whl && test -f $$themida && unzip $$themida > /dev/null || echo no themida $$themida) + @rm -rf pyarmor_cli_core-${corever}.dist-info atest: ${PYTHON} accept_test.py From d862eb7eaad17fe5b5e663f8d9d74c2801f5a760 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 23 Mar 2025 08:17:31 +0800 Subject: [PATCH 184/337] Change version to 9.1.3 --- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 9e981aa6..2a2dfc39 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.1.2' +__VERSION__ = '9.1.3' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index fb16d2f3..48de6fb7 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 1 -patch = 2 +patch = 3 ;; Compatible core version cli.core = 7.6.5 diff --git a/src/config.py b/src/config.py index 9adaf625..b7f2d3dd 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.1.2' +version = '9.1.3' # The corresponding version of pytransform.so core_version = 'r52.6' From 0bf3b1729b12bd66c6a1258cf56fd845929a5dcb Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 26 Mar 2025 23:29:00 +0800 Subject: [PATCH 185/337] change core version to 7.6.6 --- src/cli/core/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index 89dcc68a..25852281 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -20,7 +20,7 @@ # @Create Date: Thu Jan 12 17:29:25 CST 2023 # -__VERSION__ = '7.6.5' +__VERSION__ = '7.6.6' PLATFORM_NAMES = ( 'windows.x86_64', 'windows.x86', 'cygwin.x86_64', From 366c3b5867271cd46c8111030b62cee0ba9a20af Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 5 Apr 2025 16:55:21 +0800 Subject: [PATCH 186/337] Fix issue #2101 --- src/cli/register.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index 0ee04d34..4963f7bf 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -279,13 +279,15 @@ def _init_token(self, reginfo): regname = regname.encode('utf-8') product = product.encode('utf-8') notes = notes.encode('utf-8') - data = pack(' Date: Mon, 7 Apr 2025 07:53:48 +0800 Subject: [PATCH 187/337] Change version to 9.1.4 --- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 2a2dfc39..54a7ef77 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.1.3' +__VERSION__ = '9.1.4' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 48de6fb7..f36b2a46 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 1 -patch = 3 +patch = 4 ;; Compatible core version cli.core = 7.6.5 diff --git a/src/config.py b/src/config.py index b7f2d3dd..57099d3f 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.1.3' +version = '9.1.4' # The corresponding version of pytransform.so core_version = 'r52.6' From d33de60370e6b9190efd7388a44454de309acef7 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 9 Apr 2025 21:21:39 +0800 Subject: [PATCH 188/337] Add note for third-party learn platforms --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 528a2047..ffa8a145 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Please read the [Pyarmor EULA](LICENSE). - **[Report bugs][issues] following the issue template.** - **For business and security inquiries, send an email to .** -There are also some third-party learn platforms (not Pyarmor offical) +There are also some third-party learn platforms (not made by Pyarmor Team, the answer doesn't stand for Pyarmor Team's opinion) - **[Ask Pyarmor Guru][gurubase], it is a Pyarmor-focused AI to answer your questions** From e248e95d6bf13f7a90c4c25d2d17353e9a965531 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 9 Apr 2025 21:24:37 +0800 Subject: [PATCH 189/337] Refine doc --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ffa8a145..e2329f9b 100644 --- a/README.md +++ b/README.md @@ -74,9 +74,9 @@ Please read the [Pyarmor EULA](LICENSE). - **[Report bugs][issues] following the issue template.** - **For business and security inquiries, send an email to .** -There are also some third-party learn platforms (not made by Pyarmor Team, the answer doesn't stand for Pyarmor Team's opinion) +There are also some third-party learn platforms -- **[Ask Pyarmor Guru][gurubase], it is a Pyarmor-focused AI to answer your questions** +- **[Ask Pyarmor Guru][gurubase], it is a Pyarmor-focused AI to answer your questions** (not made by Pyarmor Team, the answer doesn't stand for Pyarmor Team's opinion) ## Resources From 21e6ff8d8effcadf2eab9517042ef275162d3faa Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 9 Apr 2025 21:27:12 +0800 Subject: [PATCH 190/337] Refine note --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2329f9b..b5cc188b 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Please read the [Pyarmor EULA](LICENSE). - **[Report bugs][issues] following the issue template.** - **For business and security inquiries, send an email to .** -There are also some third-party learn platforms +There is also one third-party learn platform - **[Ask Pyarmor Guru][gurubase], it is a Pyarmor-focused AI to answer your questions** (not made by Pyarmor Team, the answer doesn't stand for Pyarmor Team's opinion) From 3036dd11bffd7eb5cbe068564feb1baec505bf49 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 10 Apr 2025 22:12:36 +0800 Subject: [PATCH 191/337] Refine note --- docs/licenses.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index 57cfdc1b..8c4251d5 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -126,7 +126,7 @@ Terms of Use If you have purchased Pyarmor License for one product, but you have another product, and the total revenue of the other project is less than 100 x Pyarmor License fee, you could rent this license in your aother product - Pyarmor Pro License still could be used in CI/CD pipeline for low volume (100 runs per month) + Pyarmor Pro License could be used in CI/CD pipeline for low volume (100 runs per month, only for debug purpose) Pyarmor CI License has rate limit in CI/CD pipeline From 190b243a0bd0ad949c6140863b32519296126923 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 18 Apr 2025 22:00:37 +0800 Subject: [PATCH 192/337] change core version to 7.6.6 --- setup.py | 2 +- src/cli/default.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 9224bcd7..8af2f1bc 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,7 @@ }, install_requires=[ - 'pyarmor.cli.core~=7.6.5' + 'pyarmor.cli.core~=7.6.6' ], entry_points={ diff --git a/src/cli/default.cfg b/src/cli/default.cfg index f36b2a46..923659d3 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -6,7 +6,7 @@ minor = 1 patch = 4 ;; Compatible core version -cli.core = 7.6.5 +cli.core = 7.6.6 mini.core = 1.0 ecc.core = 1.0 vmc.core = 1.0 From 512a198eb3728d763a4dbc291db43c8c73d6d925 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Apr 2025 07:00:58 +0800 Subject: [PATCH 193/337] Fix non-ascii issue --- gh.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gh.py b/gh.py index 08fd0efe..b6b22978 100755 --- a/gh.py +++ b/gh.py @@ -429,9 +429,11 @@ def do_ad(self, arg): body = read_body('Add discussion comment:') if body: + body = body.encode("unicode_escape").decode() + # body.replace('"', r'\"') query = query_add_discussion_comment.substitute( discussionId=discussionId, - body=body.replace('"', r'\"')) + body=body) call_query(cmd_discussion_graphql, query) def do_dd(self, arg): From f66da3d0cba95852ba1189067afd5d728e73fc77 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Apr 2025 07:02:16 +0800 Subject: [PATCH 194/337] Fix non-ascii issue with gh discussion --- gh.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gh.py b/gh.py index b6b22978..57ae6f1e 100755 --- a/gh.py +++ b/gh.py @@ -429,11 +429,9 @@ def do_ad(self, arg): body = read_body('Add discussion comment:') if body: - body = body.encode("unicode_escape").decode() - # body.replace('"', r'\"') query = query_add_discussion_comment.substitute( discussionId=discussionId, - body=body) + body=body.encode("unicode_escape").decode()) call_query(cmd_discussion_graphql, query) def do_dd(self, arg): From 1b7bfe7bf1feb50dcbdd81fd4b5829ba326bb99f Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 19 Apr 2025 07:18:30 +0800 Subject: [PATCH 195/337] Change version to 9.1.5 --- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 54a7ef77..24ff1ede 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.1.4' +__VERSION__ = '9.1.5' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 923659d3..051c7895 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 1 -patch = 4 +patch = 5 ;; Compatible core version cli.core = 7.6.6 diff --git a/src/config.py b/src/config.py index 57099d3f..72bbd381 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.1.4' +version = '9.1.5' # The corresponding version of pytransform.so core_version = 'r52.6' From efb4dd18bf88de5e84ef92f74df6f7ee2daf4147 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 21 Apr 2025 07:11:09 +0800 Subject: [PATCH 196/337] End input by Ctrl+D --- gh.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gh.py b/gh.py index 57ae6f1e..bf714024 100755 --- a/gh.py +++ b/gh.py @@ -275,6 +275,7 @@ def call_query(cmd, query): def read_body(prompt): + """Input line by line, CTRL+D to end, CTRL+C to cancel""" lines = [] try: @@ -284,6 +285,8 @@ def read_body(prompt): print() lines.append(s) s = input() + except EOFError: + pass except KeyboardInterrupt: lines.clear() print('Abort by user, there is nothing to do') From 61b75dc588bd7fe0e7a96addd57322d30b0472d9 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 22 Apr 2025 23:52:15 +0800 Subject: [PATCH 197/337] change core version to 7.6.7 --- src/cli/core/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index 25852281..f72abc0d 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -20,7 +20,7 @@ # @Create Date: Thu Jan 12 17:29:25 CST 2023 # -__VERSION__ = '7.6.6' +__VERSION__ = '7.6.7' PLATFORM_NAMES = ( 'windows.x86_64', 'windows.x86', 'cygwin.x86_64', From 980088a316a9658cd4e08cdae07e7c0db92c7e69 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 25 Apr 2025 08:02:22 +0800 Subject: [PATCH 198/337] Change version to 9.1.6 --- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 24ff1ede..b6637fc0 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.1.5' +__VERSION__ = '9.1.6' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 051c7895..09d50347 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 1 -patch = 5 +patch = 6 ;; Compatible core version cli.core = 7.6.6 diff --git a/src/config.py b/src/config.py index 72bbd381..85aec843 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.1.5' +version = '9.1.6' # The corresponding version of pytransform.so core_version = 'r52.6' From 52a703ff3678d68a2c9c2ba0336a0540c70398e3 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 29 Apr 2025 10:49:35 +0800 Subject: [PATCH 199/337] Add outer error message --- docs/reference/errors.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/reference/errors.rst b/docs/reference/errors.rst index 84656ac4..77118152 100644 --- a/docs/reference/errors.rst +++ b/docs/reference/errors.rst @@ -214,4 +214,20 @@ By searching error message in google or any other search engine to find the solu __ https://github.com/dashingsoft/pyarmor/discussions/1674 +**No such file or directory: 'nul'** + +Generally something is wrong with Windows System. + +Try to create NUL device again by this command:: + + sc create null binpath=C:\Windows\System32\drivers\null.sys type=kernel start=auto error=normal + +Then start null:: + + sc start null + +If it works, this problem should be fixed. + +If not, please google the solution. It's Windows System issue. + .. include:: ../_common_definitions.txt From eb76f5a3d507f33bc4f72b0be5da7db5f5612ccb Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 30 Apr 2025 05:27:49 +0800 Subject: [PATCH 200/337] Refine doc --- docs/index.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index b7d3413a..76469f12 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,6 +8,16 @@ :Authors: |Author| :Copyright: This document has been placed in the public domain. +.. important:: + + New features introduced by Pyarmor 9.1.0 are moved to Pyarmor Learning System + + https://eke.dashingsoft.com/pyarmor/docs/en/index.html + + It includes 3 commands: `pyarmor init`, `pyarmor env`, `pyarmor build` + + And new obfuscated script types: `rft`, `mini`, `vmc`, `ecc` + How the documentation is organized ================================== @@ -23,16 +33,6 @@ How the documentation is organized * :doc:`Part 5: Licenses ` describes EULA of |Pyarmor|, the different |Pyarmor| licenses and how to purchase |Pyarmor| license. -.. important:: - - New features introduced by Pyarmor 9.1.0 are moved to Pyarmor Learning System - - https://eke.dashingsoft.com/pyarmor/docs/en/index.html - - It includes 3 commands: `pyarmor init`, `pyarmor env`, `pyarmor build` - - And new obfuscated script types: `rft`, `mini`, `vmc`, `ecc` - Getting help ============ From 740cee57cd585bf4f70ab555bd3998fe0696a324 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 30 Apr 2025 06:18:31 +0800 Subject: [PATCH 201/337] Note --mix-str doesn't touch docstring --- docs/reference/man.rst | 2 ++ docs/tutorial/advanced.rst | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/docs/reference/man.rst b/docs/reference/man.rst index f763cba9..7b023cfe 100644 --- a/docs/reference/man.rst +++ b/docs/reference/man.rst @@ -544,6 +544,8 @@ Use :term:`JIT` to process some sensitive data to improve security. Mix the string constant in scripts :sup:`basic` + This option doesn't touch any docstring + It may reduce performance if there are too many strings, in this case, only mix important strings by filter. .. seealso:: `Filter mix string` in :doc:`../tutorial/advanced` diff --git a/docs/tutorial/advanced.rst b/docs/tutorial/advanced.rst index a765717e..61ec8206 100644 --- a/docs/tutorial/advanced.rst +++ b/docs/tutorial/advanced.rst @@ -252,6 +252,10 @@ Encrypt only string length between 8 and 32 by regular expression:: Check trace log to find which strings are protected. +.. note:: + + This option doesn't touch any docstring + Filter assert function and import ================================= From 5efeee26f4cdfc0f550a584c8bb4bea385bbca65 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 30 Apr 2025 07:36:10 +0800 Subject: [PATCH 202/337] Clear error message for old license --- src/cli/register.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cli/register.py b/src/cli/register.py index 4963f7bf..4a3c46c3 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -568,6 +568,8 @@ def prepare(self, keyfile, product, upgrade=False): else: lines.append(upgrade_to_basic_info.substitute()) else: + if info['lictype'] == 'OLD': + raise CliError('old license only works for Pyarmor <= 7.7.4') if info['lictype'] not in ('BASIC', 'PRO', 'GROUP', 'CI'): raise CliError('unknown license type %s' % info['lictype']) lines.append('This license registration information will be') From 216674925403c5b8de457bc76e6b0e24797bd776 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 1 May 2025 19:40:58 +0800 Subject: [PATCH 203/337] Set encoding to utf-8 for repack hook script --- src/cli/repack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/repack.py b/src/cli/repack.py index 5a4db2a5..5338e716 100644 --- a/src/cli/repack.py +++ b/src/cli/repack.py @@ -96,7 +96,7 @@ def autoclean_output(output, autoclean=True): with open(resfile, 'wb') as f: marshal.dump(mlist + list(plist), f) -with open(hookscript, 'w') as f: +with open(hookscript, 'w', encoding='utf-8') as f: f.write("hiddenimports=[%s]" % ", ".join([repr(x) for x in hiddenimports])) ''' From fc439c4d6d7e0251f6b2a1d6bc76e9599d8b3911 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 2 May 2025 07:30:08 +0800 Subject: [PATCH 204/337] Fix format bug --- docs/topic/bccmode.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/topic/bccmode.rst b/docs/topic/bccmode.rst index 09907549..4e10bbed 100644 --- a/docs/topic/bccmode.rst +++ b/docs/topic/bccmode.rst @@ -239,6 +239,7 @@ Known issues * When format string has syntax error, BCC mode may raise `SystemError: NULL object passed to Py_BuildValue`, instead of `SyntaxError` or `ValueError`. Found in test cases `lib/python3.12/test/test_fstring.py`: + - test_invalid_syntax_error_message - test_missing_variable - test_syntax_error_for_starred_expressions From eae18d3f2db0008e31d815ccca3fde7d1fe1e16f Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 2 May 2025 12:53:17 +0800 Subject: [PATCH 205/337] Add note for bcc mode, the traceback of exception may be empty --- docs/topic/bccmode.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/topic/bccmode.rst b/docs/topic/bccmode.rst index 4e10bbed..9901dbd8 100644 --- a/docs/topic/bccmode.rst +++ b/docs/topic/bccmode.rst @@ -185,7 +185,9 @@ Here are some changed features in the BCC mode: * Some exception messages may different from the plain script. -* Most of function attributes which starts with ``__`` doesn't exists, or the value is different from the original. +* Most of function attributes which starts with ``__`` doesn't exists, or the value is different from the original. For example, there is no `__qualname__` for BCC function. + +* The traceback of exception may be empty or different from the original script Unsupported features ==================== From defe481b41918a8c6407a09dbfa75d5e1a86561c Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 2 May 2025 22:04:48 +0800 Subject: [PATCH 206/337] Change version to 9.1.7 --- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/__init__.py b/src/cli/__init__.py index b6637fc0..e6d8e89b 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.1.6' +__VERSION__ = '9.1.7' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 09d50347..6e7393d4 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 1 -patch = 6 +patch = 7 ;; Compatible core version cli.core = 7.6.6 diff --git a/src/config.py b/src/config.py index 85aec843..df413866 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.1.6' +version = '9.1.7' # The corresponding version of pytransform.so core_version = 'r52.6' From fbdf35e4dd4640cef07c06dd7c7057011d291ccf Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 2 May 2025 22:35:40 +0800 Subject: [PATCH 207/337] Doc cython with pyarmor --- docs/how-to/third-party.rst | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/how-to/third-party.rst b/docs/how-to/third-party.rst index d2919402..795ae989 100644 --- a/docs/how-to/third-party.rst +++ b/docs/how-to/third-party.rst @@ -107,6 +107,9 @@ Here are the list of problem libraries and possible solutions. You are welcome t * - `nuitka`_ - Should work with restrict_module = 0 - Not verify yet + * - `Cython`_ + - Should work with restrict_module = 0 + - .. rubric:: Footnotes @@ -185,4 +188,38 @@ Now obfuscate the scripts:: **It may still not work because of Streamlit may patch code object by itself** +Cython +------ + +Here it's an example show how to `cythonize` a python script `foo.py` obfuscated +by pyarmor:: + + print('Hello Cython') + +First obfuscate it with some extra options:: + + $ pyarmor cfg restrict_module=0 + $ pyarmor gen foo.py + $ ls dist/ + foo.py pyarmor_runtime_000000 + +The obfuscated script and runtime files will be saved in the path `dist` + +Next `cythonize` the obfuscated script `dist/foo.py` to `foo.c`:: + + $ cd dist + $ cythonize -3 foo.py + +Then compile `foo.c` to the extension modules(it may need extra cfalg ``-fPIC`` in some platforms):: + + $ gcc -shared $(python-config --cflags) $(python-config --ldflags) \ + -o foo$(python-config --extension-suffix) foo.c + +Finally test it, remove `dist/foo.py` and import the extension module:: + + $ rm foo.py + $ python -c 'import foo' + +It will print `Hello Cython` as expected. + .. include:: ../_common_definitions.txt From f337fefca0e03299d70058a9497dba58592a741f Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 3 May 2025 05:49:19 +0800 Subject: [PATCH 208/337] Doc sys.exception is None in exception handler for BCC mode --- docs/topic/bccmode.rst | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/docs/topic/bccmode.rst b/docs/topic/bccmode.rst index 9901dbd8..d073ff33 100644 --- a/docs/topic/bccmode.rst +++ b/docs/topic/bccmode.rst @@ -187,7 +187,29 @@ Here are some changed features in the BCC mode: * Most of function attributes which starts with ``__`` doesn't exists, or the value is different from the original. For example, there is no `__qualname__` for BCC function. -* The traceback of exception may be empty or different from the original script +* In the exception handler, `sys.exception()` will return `None`, so the functions depended on `sys.exception` may not work. For example + +.. code-block:: python + + import traceback + + def main(): + try: + 1 / 0 + except Exception as e: + + # In BCC mode, sys.exception() will return None, the output is: + # None + print(sys.exception()) + + # It doesn't work in BCC mode, the output is "NoneType: None" + traceback.print_exc() + + # The traceback will be printed by this form in BCC mode + # But the traceback doesn't include line no. and source + traceback.print_exception(e) + + main() Unsupported features ==================== @@ -218,7 +240,6 @@ And unsupported functions: * super * locals * sys._getframe -* sys.exc_info For example, the following functions are not obfuscated by BCC mode, because they use unsupported features or unsupported functions: From f02a90be3da49a797146ddc973506594a3ed2a7d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 16 May 2025 09:57:44 +0800 Subject: [PATCH 209/337] Refine doc for old license --- docs/licenses.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index 8c4251d5..73a6fd1a 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -179,7 +179,9 @@ Open `Pyarmor Shopping Cart`_ in any web browser: https://jondy.github.io/paypal/index.html -All of these license are only for Python 3.7+, if need Python < 3.7, please purchase `old license ` +All of these license are only for Pyarmor 8.0 and Python 3.7+, if need work with Pyarmor 7.x which supports Python < 3.7, please purchase `Pyarmor Old License`: + + https://jondy.github.io/paypal/obsolete.html Refund policy ============= From 08d3f0731f53bf2dfa71f118e1a4851fd72fb170 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 16 May 2025 09:58:19 +0800 Subject: [PATCH 210/337] Refine doc --- docs/licenses.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index 73a6fd1a..e980a083 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -179,7 +179,7 @@ Open `Pyarmor Shopping Cart`_ in any web browser: https://jondy.github.io/paypal/index.html -All of these license are only for Pyarmor 8.0 and Python 3.7+, if need work with Pyarmor 7.x which supports Python < 3.7, please purchase `Pyarmor Old License`: +All of these license are only for Pyarmor 8.0+ with Python 3.7+, if need work with Pyarmor 7.x which supports Python < 3.7, please purchase `Pyarmor Old License`: https://jondy.github.io/paypal/obsolete.html From b3b6835b94c897ed655430284dba7a4c5a8fc510 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 16 May 2025 10:54:44 +0800 Subject: [PATCH 211/337] Refine error message --- src/cli/register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index 4a3c46c3..79d3bdb2 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -208,7 +208,7 @@ def parse_keyfile(self, filename): if len(line) == 192 and line.find(' ') == -1: return regname, line - raise CliError('no registration code found in %s' % filename) + raise CliError('invalid activation file "%s"' % filename) def _register_offline_license(self, fzip, namelist): logger.info('machine id in group license: %s', ', '.join( From c048f5efaae7296eafe680f236237128994f0060 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 20 May 2025 04:59:15 +0800 Subject: [PATCH 212/337] Not support upgarde Pro to CI now --- docs/how-to/register.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 8fc17c2d..8266e094 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -478,10 +478,7 @@ But in the following versions something is changed - If using Pyarmor in CI/CD pipeline, 2 choices - Still use Pyarmor 8.x as before - - Upgrade to Pyarmor 9, it need upgrade :term:`Pyarmor Pro` to :term:`Pyarmor CI` - - - :term:`Pyarmor CI` is paid per year - - Please contact pyarmor@163.com if need upgrade to :term:`Pyarmor CI` + - Upgrade to Pyarmor 9, and purchase new :term:`Pyarmor CI` - :term:`Pyarmor Group` From 490528c61ca5bb27c796aca31bf913aaea1e2e4a Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 20 May 2025 05:49:06 +0800 Subject: [PATCH 213/337] Doc about upgrading Pyarmor License --- docs/how-to/register.rst | 2 ++ docs/licenses.rst | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 8266e094..68451643 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -449,6 +449,8 @@ When need many Pyarmor Licenses in one machine, set each license to different pa $ pyarmor --home ~/.pyarmor2 reg pyarmor-regfile-2052.zip $ pyarmor --home ~/.pyarmor2 gen project2/foo.py +.. _pyarmor: + What need to do after upgrading Pyarmor ======================================= diff --git a/docs/licenses.rst b/docs/licenses.rst index e980a083..c3b963b4 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -348,6 +348,22 @@ How many licenses are required - Register each product with different license once - Use one of registeration file to generate the obfuscated scripts for all the products +.. _how-to-upgrade-license: + +How to upgrade Pyarmor Licenses +------------------------------- + +Now it doesn't support to upgrade one Pyarmor License to any other Pyarmor License. For example, upgrade Pyarmor Basic to Pyarmor Pro by paying price difference + +There are only the following special cases + +- Pyarmor Old License used by Pyarmor <= 7.x could be upgrade to Pyarmor Basic License in some conditions +- If upgrading Pyarmor v8 to v9, Pyarmor License need to be re-activated + +Please refer to `What need to do after upgrading Pyarmor `_ + +In short, if it could be upgraded successfully by the guide in the documentation, it's OK. If something is wrong, this license can't be upgraded. Pyarmor Team doesn't handle this kind of request, there may be no any reply. + Q & A ----- From 95b751030ecbbf59f20065bb82760e92714c8e65 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 22 May 2025 09:41:24 +0800 Subject: [PATCH 214/337] change core version to 7.6.7 --- setup.py | 2 +- src/cli/default.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 8af2f1bc..253ab7c7 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,7 @@ }, install_requires=[ - 'pyarmor.cli.core~=7.6.6' + 'pyarmor.cli.core~=7.6.7' ], entry_points={ diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 6e7393d4..adf2322d 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -6,7 +6,7 @@ minor = 1 patch = 7 ;; Compatible core version -cli.core = 7.6.6 +cli.core = 7.6.7 mini.core = 1.0 ecc.core = 1.0 vmc.core = 1.0 From 3cbaf0cc0d7404a585cd32d92a915368822fd088 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 22 May 2025 10:37:05 +0800 Subject: [PATCH 215/337] Change version to 9.1.8 --- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/__init__.py b/src/cli/__init__.py index e6d8e89b..72f78283 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.1.7' +__VERSION__ = '9.1.8' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index adf2322d..7d497b77 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 1 -patch = 7 +patch = 8 ;; Compatible core version cli.core = 7.6.7 diff --git a/src/config.py b/src/config.py index df413866..932e0bcf 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.1.7' +version = '9.1.8' # The corresponding version of pytransform.so core_version = 'r52.6' From ff7349ad7fdabdbd3b48f12b0f7ee24e4cce50df Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 24 May 2025 05:41:08 +0800 Subject: [PATCH 216/337] Remove debug purpose usage for Pro License to avoid confusion --- docs/licenses.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index c3b963b4..30a488bb 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -126,8 +126,6 @@ Terms of Use If you have purchased Pyarmor License for one product, but you have another product, and the total revenue of the other project is less than 100 x Pyarmor License fee, you could rent this license in your aother product - Pyarmor Pro License could be used in CI/CD pipeline for low volume (100 runs per month, only for debug purpose) - Pyarmor CI License has rate limit in CI/CD pipeline In details check `Pyarmor EULA`_ From 41b7f1d3b49ff8ee6188612dee34c8a5f79f0ba8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 27 May 2025 15:08:02 +0800 Subject: [PATCH 217/337] Add link to ask page in pyarmor learning system --- docs/questions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/questions.rst b/docs/questions.rst index ed51fb32..1b9821d8 100644 --- a/docs/questions.rst +++ b/docs/questions.rst @@ -12,7 +12,7 @@ If using :command:`pyarmor-7` or Pyarmor < 8.0, please check `Pyarmor 7.x Doc`_ .. important:: - Pyarmor team handles too many wrong usage issues, so one document :doc:`reference/solutions` has been arranged to solve this kind of issue quickly. If you aren't sure this issue is wrong usage or not, please check this doc at first. + Pyarmor team handles too many wrong usage issues, so one document :doc:`reference/solutions` has been arranged to solve this kind of issue quickly. If you aren't sure this issue is wrong usage or not, please check this doc or https://eke.dashingsoft.com/pyarmor/ask/ at first. Pyarmor team will mark this kind of issue as `invalid` or `documented` and close it immediately. From 8c97b2ead25ed182522acf2e893f3ce77619995f Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 19 Jun 2025 09:41:56 +0800 Subject: [PATCH 218/337] Note how to install old version of pyarmor --- docs/tutorial/installation.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/tutorial/installation.rst b/docs/tutorial/installation.rst index 0ca1bc96..db21e598 100644 --- a/docs/tutorial/installation.rst +++ b/docs/tutorial/installation.rst @@ -71,6 +71,14 @@ Not all the platforms are supported, more information check :doc:`../reference/e $ pip install pyarmor.cli +.. note:: + + If need install old version Pyarmor, just specify the exact version. For example:: + + $ pip install pyarmor==8.5.12 + + For more information, please check `pip` doc + Installed command ----------------- From 176a984ef8333f1ce0ba9481c554da916c794583 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 21 Jul 2025 22:10:08 +0800 Subject: [PATCH 219/337] Change version to 9.1.9 --- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 72f78283..26da3ae8 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.1.8' +__VERSION__ = '9.1.9' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 7d497b77..4c80da68 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 1 -patch = 8 +patch = 9 ;; Compatible core version cli.core = 7.6.7 diff --git a/src/config.py b/src/config.py index 932e0bcf..b634fcd4 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.1.8' +version = '9.1.9' # The corresponding version of pytransform.so core_version = 'r52.6' From 2d2f02df884b43e42d44cfebabee49c27848d6b2 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 20 Aug 2025 10:33:37 +0800 Subject: [PATCH 220/337] Add note for --bind-data --- docs/reference/man.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference/man.rst b/docs/reference/man.rst index 7b023cfe..cfae8ebe 100644 --- a/docs/reference/man.rst +++ b/docs/reference/man.rst @@ -367,6 +367,8 @@ If DATA has a leading ``@``, then the rest is a filename. Pyarmor reads the bina For any other case, DATA is converted to bytes as private data. +By special builtin function :func:`__pyarmor__`, the bind data can be read in the obfuscated scripts. Refer to this example `Using hook to bind script to docker ` in the :doc:`../tutorial/customization` + .. option:: --period N Check :term:`Runtime Key` periodically. From d9162e07df79ee18c22bb1e00da4604d5e7a1276 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 5 Sep 2025 09:39:51 +0800 Subject: [PATCH 221/337] Fix typo #2204 --- src/cli/register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index 79d3bdb2..ebed17c7 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -445,7 +445,7 @@ def __str__(self): ] if lictype == 'trial': self.notes.append('* Can\'t obfuscate big script and mix str') - elif lictype in ('bacic', 'pro'): + elif lictype in ('basic', 'pro'): self.notes.append('* Need verify license online ' 'when obfuscating scripts') elif lictype == 'group': From affa35b0bdee8742239d5c51b039a38a5908d5a3 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 10 Sep 2025 19:59:10 +0800 Subject: [PATCH 222/337] Add note for option --assert-call and --assert-import --- docs/reference/man.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/man.rst b/docs/reference/man.rst index cfae8ebe..cf4d7d0e 100644 --- a/docs/reference/man.rst +++ b/docs/reference/man.rst @@ -554,7 +554,7 @@ It may reduce performance if there are too many strings, in this case, only mix .. option:: --assert-call - Assert function is obfuscated + Assert function is obfuscated, this option implies :option:`--private`. If this option is enabled, Pyarmor scans each function call in the scripts. If the called function is in the obfuscated scripts, protect it as below, and leave others as it is. For example, @@ -586,7 +586,7 @@ In this example, ``fib`` is protected, ``print`` is not. .. option:: --assert-import - Assert module is obfuscated + Assert module is obfuscated, this option implies :option:`--private`. If this option is enabled, Pyarmor scans each ``import`` statement in the scripts. If the imported module is obfuscated, protect it as below, and leave others as it is. For example, From 4d402e1f78723e92d077afc0727b02efcc364e0d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 10 Sep 2025 20:09:53 +0800 Subject: [PATCH 223/337] Add build option: readonly_module --- src/cli/default.cfg | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 4c80da68..947ac920 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -174,8 +174,15 @@ obf_code = 1 ;; Since Python 3.12, "2" is same as "1", both of them use full wrap mode wrap_mode = 1 +;; 1: the obfuscated scripts can't be modified +;; 2: private module, can't be imported by plain scripts +;; 3: restrict module, module attribute is hidden for outer scripts +;; restrict_module = 1 +;; if set to 1, module attribute can't be written by plain scripts +readonly_module = 0 + ;; ;; Advanced features ;; From 4bafb418740a8b000fa02f58510f995e7fdd8677 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 10 Sep 2025 20:26:13 +0800 Subject: [PATCH 224/337] change core version to 7.6.8 --- src/cli/core/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index f72abc0d..7c0bfbfa 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -20,7 +20,7 @@ # @Create Date: Thu Jan 12 17:29:25 CST 2023 # -__VERSION__ = '7.6.7' +__VERSION__ = '7.6.8' PLATFORM_NAMES = ( 'windows.x86_64', 'windows.x86', 'cygwin.x86_64', From 1426c15ad1ba390f4693687604d92b387b98678f Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 11 Sep 2025 09:34:58 +0800 Subject: [PATCH 225/337] change core version to 7.6.8 --- setup.py | 2 +- src/cli/default.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 253ab7c7..2d66e46e 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,7 @@ }, install_requires=[ - 'pyarmor.cli.core~=7.6.7' + 'pyarmor.cli.core~=7.6.8' ], entry_points={ diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 947ac920..00846bc4 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -6,7 +6,7 @@ minor = 1 patch = 9 ;; Compatible core version -cli.core = 7.6.7 +cli.core = 7.6.8 mini.core = 1.0 ecc.core = 1.0 vmc.core = 1.0 From bae6b4d391ff1dc06774ecb85088a597a89036f5 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 11 Sep 2025 10:42:32 +0800 Subject: [PATCH 226/337] Doc readonly module and usage --- docs/tutorial/obfuscation.rst | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/tutorial/obfuscation.rst b/docs/tutorial/obfuscation.rst index 07990ca1..b4d54c20 100644 --- a/docs/tutorial/obfuscation.rst +++ b/docs/tutorial/obfuscation.rst @@ -172,6 +172,42 @@ Now do above test again, it should work:: ... import joker OK ... import joker.queens +.. _using readonly module: + +Using readonly module +--------------------- + +.. versionadded:: 8.1.9 + +Readonly module is one simple way to protect the obfuscated package, it only allows the plain scripts import and read the obfuscated module, but can't write or change any attribute or method of obfuscated modules. + +Enable readonly module by this way:: + + $ pyarmor cfg readonly_module=1 + +Then obfuscate the whole package:: + + $ pyarmor gen --enable-jit --mix-str joker/ + +Test it:: + + $ cd dist + $ python + >>> import joker + >>> dir(joker) + >>> joker.aaa = 1 + Traceback (most recent call last): + File "", line 1, in + RuntimeError: protection exception (16782406) + +If only need export a few modules, for example, `joker.card` and `joker.__init__`, the other modules in the package need not be exported, the best way is like these:: + + $ pyarmor cfg readonly_module=1 + $ pyarmor cfg exclude_restrict_modules="__init__ joker.card" + $ pyarmor gen --enable-jit --mix-str --assert-call --assert-import --restrict joker/ + +The modules list in the `exclude_restrict_modules` are readonly, all the others are more restricted. + Copying package data files ========================== From e28be6b2deaaf98e87ed74f55d54abdfcafb5211 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 11 Sep 2025 16:30:01 +0800 Subject: [PATCH 227/337] Remove out of date link --- docs/licenses.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index 30a488bb..51330409 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -186,19 +186,12 @@ Refund policy If activation file isn't used, and purchasing date is in 30 days, refund is acceptable. -- If purchasing order from MyCommerce: - - 1. Email to Ordersupport@mycommerce.com with order information and ask for refund. - 2. Or click `FindMyOrder page`_ to submit refund request - - If purchasing order from reseller, contact your reseller - For other cases, email to pyarmor@163.com Out of 30 days, or activation file has been used, refund request will be rejected. -.. _FindMyOrder page: https://www.findmyorder.com/store?Action=DisplayEmailCustomerServicePage&Env=BASE&Locale=en_US&SiteID=findmyor - Appendix ======== From 7a06f4bd22d19dd22dc8ed2199f99d4660c547bb Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 12 Sep 2025 10:52:42 +0800 Subject: [PATCH 228/337] Fix typo --- docs/tutorial/obfuscation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/obfuscation.rst b/docs/tutorial/obfuscation.rst index b4d54c20..7fe0553f 100644 --- a/docs/tutorial/obfuscation.rst +++ b/docs/tutorial/obfuscation.rst @@ -177,7 +177,7 @@ Now do above test again, it should work:: Using readonly module --------------------- -.. versionadded:: 8.1.9 +.. versionadded:: 9.1.9 Readonly module is one simple way to protect the obfuscated package, it only allows the plain scripts import and read the obfuscated module, but can't write or change any attribute or method of obfuscated modules. From 19d2e6df21fbd9a1fa47334051a780cb8783a892 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 17 Sep 2025 18:59:57 +0800 Subject: [PATCH 229/337] Update refund policy --- docs/licenses.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/licenses.rst b/docs/licenses.rst index 51330409..8f06150c 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -190,6 +190,8 @@ If activation file isn't used, and purchasing date is in 30 days, refund is acce - For other cases, email to pyarmor@163.com +- Pyarmor Team may charge 5% fee for some refund cases. + Out of 30 days, or activation file has been used, refund request will be rejected. Appendix From d4570d1d5c9cc2debf51c4f71dddcd6a7201be3c Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 22 Sep 2025 10:00:33 +0800 Subject: [PATCH 230/337] Add one example to show how users can configure Docker networking so the host and container are guaranteed to share the same subnet --- docs/how-to/register.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 68451643..cfd8b2b0 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -399,7 +399,15 @@ If there is IPv4 Address, for example ``172.22.32.1``, which is in the same netw Anyway, `pyarmor-auth` must listen on any IPv4 address which is in the same network as docker container. -If there is no available IPv4 address in Windows, the other solution is running `pyarmor-auth` in WSL, in this case, WSL should be taken as offline device. +If there is no available IPv4 address in Windows, the other solution is running `pyarmor-auth` in WSL, in this case, WSL should be taken as offline device. For example:: + + # Create a custom Docker bridge network with a defined subnet + docker network create --subnet=172.17.0.0/16 pyarmor-net + + # Run the container on this network and point host.docker.internal at the gateway + docker run --network pyarmor-net --add-host host.docker.internal=172.17.0.1 ... + +This makes it explicit that the license check requires host and container to be in the same subnet, not just routable. **When something is wrong** From 7e5d5fcbc3b6f5cec1a251d2debd65fd0e513947 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 22 Sep 2025 15:15:42 +0800 Subject: [PATCH 231/337] Doc eroor `invalid activation file` --- docs/reference/errors.rst | 4 ++++ docs/reference/solutions.rst | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/docs/reference/errors.rst b/docs/reference/errors.rst index 77118152..fe54c6dd 100644 --- a/docs/reference/errors.rst +++ b/docs/reference/errors.rst @@ -93,6 +93,10 @@ Building Errors b'NO:missing parameter' If not return this, but raises exception, it's firewall problem, please configure it to allow Python interpreter to visit `pyarmor.dashingsoft.com:80` + * - invalid activation file + - Make sure it's the attachment of activation email which title likes `[Pyarmor] License Activation Code` + Make sure it's not changed by your email server, it should be same as the email content which starts with `Dear ...` and includes the activation code in one line (it's important there should no line break in the activation code). + Runtime Errors ============== diff --git a/docs/reference/solutions.rst b/docs/reference/solutions.rst index 74d4018d..e266b729 100644 --- a/docs/reference/solutions.rst +++ b/docs/reference/solutions.rst @@ -99,6 +99,13 @@ Registration Failed If it's using :term:`Activation File` (`pyarmor-regcode-xxxx.txt`), make sure this file is not used more than 3 times, generally once initial registration completed, activation file :file:`pyarmor-regcode-xxxx.txt` is invalid. It should use :term:`Registration File` ``pyarmor-regfile-xxxx.zip`` for any next registration. +**Error: invalid activation file** + +It means that the activation file is not issued by Pyarmor Team, please make sure: + +- It's exactly the attachment of activation email which title is `[Pyarmor] License Activation Code` and this attchment is NOT modified by your mail server +- The content of activation file should be same as the email content which starts with `Dear ...` and includes the activation code in one line (it's important there is no line break in the activation code). + **Basic/Pro License** 1. If the date time of this device has been changed, restore it to current time From bbb7163d4d9733b62b688499e72e62d4da86fd83 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 26 Sep 2025 12:28:51 +0800 Subject: [PATCH 232/337] Doc hardware prefix for --bind-device --- docs/reference/man.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/reference/man.rst b/docs/reference/man.rst index cf4d7d0e..ec61e316 100644 --- a/docs/reference/man.rst +++ b/docs/reference/man.rst @@ -357,6 +357,18 @@ In Linux, binding to specify name:: $ pyarmor gen -b "/dev/vda2:KDX3298FS6P5AX380" foo.py +It also supports prefix for each hardware type, for example:: + + $ pyarmor gen -b "*HARDDISK:0000_1111_2222_3333 *IFIPV4:11:22:33:44:55:66" + +This could fix some runtime key failed issues. All supported prefix are + +- ``*MID:`` +- ``*IFMAC:`` +- ``*IFIPV4:`` +- ``*DOMAIN:`` +- ``*HARDDISK:`` + .. option:: --bind-data DATA DATA may be ``@FILENAME`` or string From 0a5a3383d4b18d3c904e507f3c96f0339d6157d8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 26 Sep 2025 21:32:24 +0800 Subject: [PATCH 233/337] Doc another solution for docker container with pyarmor-auth --- docs/how-to/register.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index cfd8b2b0..b2dce51c 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -409,6 +409,8 @@ If there is no available IPv4 address in Windows, the other solution is running This makes it explicit that the license check requires host and container to be in the same subnet, not just routable. +Another solution is to run docker with `--network=host`, it make sure docker container has the same netwok netmask with host. + **When something is wrong** 1. Check docker container network: From a00354a0efb5a540aea4fe602d826945f2b82170 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 26 Sep 2025 22:15:58 +0800 Subject: [PATCH 234/337] Change version to 9.2.0 --- src/cli/__init__.py | 2 +- src/cli/default.cfg | 4 ++-- src/config.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 26da3ae8..70421870 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.1.9' +__VERSION__ = '9.2.0' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 00846bc4..18a504d4 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -2,8 +2,8 @@ ;; Pyarmor version major = 9 -minor = 1 -patch = 9 +minor = 2 +patch = 0 ;; Compatible core version cli.core = 7.6.8 diff --git a/src/config.py b/src/config.py index b634fcd4..dfc6b034 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.1.9' +version = '9.2.0' # The corresponding version of pytransform.so core_version = 'r52.6' From 3ecbd0f49c7947f1f1d9d50231b38efd8aec1178 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 15 Oct 2025 10:43:53 +0800 Subject: [PATCH 235/337] Fix example error --- docs/reference/man.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference/man.rst b/docs/reference/man.rst index ec61e316..1800dc11 100644 --- a/docs/reference/man.rst +++ b/docs/reference/man.rst @@ -338,11 +338,11 @@ Using this options multiple times means binding many machines. For example, the In case there are more network cards, binding anyone by this form:: - $ pyarmor gen -b "<2a:33:50:46:8f>" foo.py + $ pyarmor gen -b "*IFMAC:<2a:33:50:46:8f>" foo.py Bind all network cards by this form:: - $ pyarmor gen -b "<2a:33:50:46:8f,f0:28:69:c0:24:3a>" foo.py + $ pyarmor gen -b "*IFMAC:<2a:33:50:46:8f,f0:28:69:c0:24:3a>" foo.py In Linux, it's possible to bind named Ethernet card:: @@ -359,7 +359,7 @@ In Linux, binding to specify name:: It also supports prefix for each hardware type, for example:: - $ pyarmor gen -b "*HARDDISK:0000_1111_2222_3333 *IFIPV4:11:22:33:44:55:66" + $ pyarmor gen -b "*HARDDISK:0000_1111_2222_3333 *IFMAC:11:22:33:44:55:66" This could fix some runtime key failed issues. All supported prefix are From d95d6677a570c1d60c1b7dbf0e2c06b207163e83 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 17 Oct 2025 08:56:44 +0800 Subject: [PATCH 236/337] Add new build option: enable_vmcode --- src/cli/default.cfg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 18a504d4..a9a4fcdf 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -147,6 +147,11 @@ enable_jit = 0 enable_bcc = 0 enable_rft = 0 +;; Enable opcode virtual protection +;; This feature is meaningless in BCC mode +;; If value is -1, it's enabled for Python 3.14+, and disabled for the priors +enable_vmcode = -1 + ;; assert: call import assert_call = 0 assert_import = 0 From 7497a4efa2e46c587fea1766e19de21d56a66acf Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 17 Oct 2025 08:58:43 +0800 Subject: [PATCH 237/337] Enable Python 3.14 --- src/cli/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index a42e6141..cc93e367 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -764,7 +764,7 @@ def main_entry(argv): log_settings(ctx, args) x, y = sys.version_info[:2] - if not (x == 3 and y > 6 and y < 14): + if not (x == 3 and y > 6 and y < 15): raise CliError('Python %s.%s is not supported' % (x, y)) if args.version: From 83ecac5336529739bdb52e6dbbba333aac01c841 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 20 Oct 2025 16:12:37 +0800 Subject: [PATCH 238/337] Doc rate limits for CI License --- docs/licenses.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/licenses.rst b/docs/licenses.rst index 8f06150c..c7d50e16 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -126,7 +126,12 @@ Terms of Use If you have purchased Pyarmor License for one product, but you have another product, and the total revenue of the other project is less than 100 x Pyarmor License fee, you could rent this license in your aother product - Pyarmor CI License has rate limit in CI/CD pipeline + Pyarmor CI License has rate limit in CI/CD pipeline: + + - 1 run per second + - 100 runs per hour + - 1,000 runs per day + - 10,000 runs per month In details check `Pyarmor EULA`_ From 245ba8f19d8c8e2e9a53b6d697b46c387ddd7183 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 20 Oct 2025 16:30:53 +0800 Subject: [PATCH 239/337] Doc rate limits in how-to guide --- docs/how-to/ci.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 059f4ee9..6fafa0dd 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -53,7 +53,13 @@ Notes .. important:: - In CI/CD pipeline, each run will send license and docker information to Pyarmor License Server, excessive requests or requests beyond normal usage may be rejected by Pyarmor License Server + In CI/CD pipeline, each run will send license and docker information to Pyarmor License Server, excessive requests or requests beyond normal usage may be rejected by Pyarmor License Server. Generally do not exceed any of these rate limits: + + - 1 run per second + - 100 runs per hour + - 1,000 runs per day + - 10,000 runs per month + It's not allowed to install Pyarmor in your customer's docker image From 87e334f816471ec6e7d2c8e54e525580b7139985 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 24 Oct 2025 18:26:53 +0800 Subject: [PATCH 240/337] Diable vmcode mode by default --- src/cli/default.cfg | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index a9a4fcdf..4f962fdb 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -149,8 +149,7 @@ enable_rft = 0 ;; Enable opcode virtual protection ;; This feature is meaningless in BCC mode -;; If value is -1, it's enabled for Python 3.14+, and disabled for the priors -enable_vmcode = -1 +enable_vmcode = 0 ;; assert: call import assert_call = 0 From 0ec868ad0011f73f403a1d0d9ad13cab8857fd36 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 25 Oct 2025 20:54:43 +0800 Subject: [PATCH 241/337] Update doc version --- docs/conf.py | 6 +++--- docs/index.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index b2abf94b..ba255ae1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,7 +49,7 @@ # General information about the project. project = 'Pyarmor' -copyright = '2018 - 2024 Dashingsoft Corp.' +copyright = '2018 - 2025 Dashingsoft Corp.' author = 'Jondy Zhao' # The version info for the project you're documenting, acts as replacement for @@ -57,9 +57,9 @@ # built documents. # # The short X.Y version. -version = '9.0' +version = '9.2' # The full version, including alpha/beta/rc tags. -release = '9.0.8' +release = '9.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/index.rst b/docs/index.rst index 76469f12..03e0cd3f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,7 +10,7 @@ .. important:: - New features introduced by Pyarmor 9.1.0 are moved to Pyarmor Learning System + New features introduced by Pyarmor 9.1.0 are also availabled here https://eke.dashingsoft.com/pyarmor/docs/en/index.html From 8237d317b6d35c0dec137651c1b02d69d3b9dd83 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 25 Oct 2025 20:55:18 +0800 Subject: [PATCH 242/337] Doc CI License usage with rate limits --- docs/how-to/ci.rst | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 6fafa0dd..07558531 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -4,6 +4,14 @@ Using Pyarmor in CI Pipeline ============================== +There are 2 ways to use Pyarmor in CI/CD pipeline: + +- Direct way, it's simple, but only works for Trial, Basic and CI license, and there is rate limits +- Indirect way, it need change the original build workflow, but works for any type license + +Direct Way +========== + **Trial Version** could be used in CI/CD pipeline by one step:: pip install pyarmor @@ -60,10 +68,40 @@ Notes - 1,000 runs per day - 10,000 runs per month + If exceeds any of these limitions, please check the section `High frequency use solution` It's not allowed to install Pyarmor in your customer's docker image -:term:`Pyarmor Pro` and :term:`Pyarmor Group` License can't be used in CI/CD pipeline directly, but there is one workaround +When need to request new CI regfile +----------------------------------- + +In the following cases, it need request one new CI regfile + +- After :term:`Pyarmor CI` License is expired, all the previous CI regfiles don't work any longer. After the renewal is successful, it need request new CI regfile ``pyarmor-ci-N.zip`` +- After Pyarmor is upgrade one new major/minor version, the old CI regfile may not work in the latest version (but it still works with old Pyarmor version). It need request one new CI regfile by the version. Note that the patch number has no effect for this case, for example, from v9.1.3 to v9.1.8, nothing changed. + +High frequency use solution +--------------------------- + +.. versionadded:: 9.2.0 + +For daily build, it need special workflow. + +Pyarmor team need verify the project, and understand how many runs per hour and per month probably. + +Generally, it should be no more than 1,000 runs per hour. + +Each month the free quota is 10, 000 runs, if need more quota, it need extra fees: + +- 100,000 per month, extra fee: $10 for one year +- 200,000 per month, extra fee: $20 for one year +- 300,000 per month, extra fee: $30 for one year +- ... + +Indirect Way +============ + +:term:`Pyarmor Pro` and :term:`Pyarmor Group` License can't be used in CI/CD pipeline directly, but this works - First obfuscate the scripts in local device and store them to another branch like `master-obf` - Then in CI/CD pipeline to check this new branch From 5e3368c29bcf8f51cd47b051f17996249ae76cc2 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 26 Oct 2025 09:21:13 +0800 Subject: [PATCH 243/337] Change release plan for Python 3.14 --- docs/ReleasePlan.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md index aa8a9837..81492bab 100755 --- a/docs/ReleasePlan.md +++ b/docs/ReleasePlan.md @@ -86,6 +86,12 @@ Status: developing **9.2** +Status: developing, released on 2025-12-01, 2 months after Python 3.14 is released + +- Support Python 3.14 which will be released on 2025-10-01 as plan + +**9.3** + Status: not start - ECC mode, mini BCC mode, only transform part of function body to C code @@ -98,16 +104,10 @@ The following features are cancelled because ECC/VMC mode are introduced: - BCC mode support cross-platform (may not if too complex) (cancel) - Obfuscate string constants in extension module "pyarmor_runtime" (cancel) -**9.3** +**9.4** Status: not start - Learning system to help users learning and using Pyarmor -**9.4** - -Status: plan, released on 2025-12-01, 2 months after Python 3.14 is released - -- Support Python 3.14 which will be released on 2025-10-01 as plan - Pyarmor features will be stable in 2025 From a19cd34f8b062ba4b782a15172084602b87cb395 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 28 Oct 2025 10:33:18 +0800 Subject: [PATCH 244/337] change core version to 8.0.0 --- setup.py | 2 +- src/cli/core/__init__.py | 2 +- src/cli/default.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 2d66e46e..4054ea55 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,7 @@ }, install_requires=[ - 'pyarmor.cli.core~=7.6.8' + 'pyarmor.cli.core~=8.0.0' ], entry_points={ diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index 7c0bfbfa..01f939b3 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -20,7 +20,7 @@ # @Create Date: Thu Jan 12 17:29:25 CST 2023 # -__VERSION__ = '7.6.8' +__VERSION__ = '8.0.0' PLATFORM_NAMES = ( 'windows.x86_64', 'windows.x86', 'cygwin.x86_64', diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 4f962fdb..774be799 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -6,7 +6,7 @@ minor = 2 patch = 0 ;; Compatible core version -cli.core = 7.6.8 +cli.core = 8.0.0 mini.core = 1.0 ecc.core = 1.0 vmc.core = 1.0 From 85b8a89434e0b9784472ef625b8971e811c903c8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 28 Oct 2025 10:33:49 +0800 Subject: [PATCH 245/337] Enable Python 3.14 and 3.15 supports --- src/cli/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index cc93e367..ae4a9e6e 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -764,7 +764,7 @@ def main_entry(argv): log_settings(ctx, args) x, y = sys.version_info[:2] - if not (x == 3 and y > 6 and y < 15): + if not (x == 3 and y > 6 and y < 16): raise CliError('Python %s.%s is not supported' % (x, y)) if args.version: From 009289d50432417383b5c77bf5d6ad3514a62def Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 28 Oct 2025 14:59:10 +0800 Subject: [PATCH 246/337] Add test versions for Python 3.14 and 3.15 --- tests.8/Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests.8/Makefile b/tests.8/Makefile index e51aa08a..f8f809fb 100644 --- a/tests.8/Makefile +++ b/tests.8/Makefile @@ -3,10 +3,12 @@ HOSTS := darwin.x86_64 darwin.arm64 linux.x86_64 linux.aarch64 linux.x86 windows UNAME = $(shell uname) ARCH := $(shell arch) +ALL_PYVERS := 3.7 3.8 3.9 3.10 3.11 3.12 3.13 3.14 3.15 + ifeq ($(findstring Linux,$(UNAME)),Linux) ifeq ($(ARCH),x86_64) HOST ?= linux.x86_64 - PYVERS := 3.7 3.8 3.9 3.10 3.11 3.12 3.13 + PYVERS := ${ALL_PYVERS} PYTHONBIN := /usr/local/bin/pythonXY else ifeq ($(ARCH),aarch64) HOST ?= linux.aarch64 @@ -14,7 +16,7 @@ ifeq ($(findstring Linux,$(UNAME)),Linux) PYTHONBIN := /usr/local/bin/pythonXY else HOST ?= linux.x86 - PYVERS := 3.7 3.8 3.9 3.10 3.11 3.12 3.13 + PYVERS := ${ALL_PYVERS} PYTHONBIN := /usr/local/bin/pythonXY endif else ifeq ($(findstring Darwin,$(UNAME)),Darwin) @@ -24,7 +26,7 @@ else ifeq ($(findstring Darwin,$(UNAME)),Darwin) PYTHONBIN := /usr/local/bin/pythonXY else HOST ?= darwin.x86_64 - PYVERS := 3.7 3.8 3.9 3.10 3.11 3.12 3.13 + PYVERS := ${ALL_PYVERS} PYTHONBIN := /usr/local/bin/pythonXY endif else From 64d9f2df809d65d4acc3bd75829b06f06a3fb3d1 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 29 Oct 2025 22:28:21 +0800 Subject: [PATCH 247/337] Increase mini.core to v2 for Python 3.14/3.15 --- src/cli/default.cfg | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 774be799..cb43499a 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -7,9 +7,10 @@ patch = 0 ;; Compatible core version cli.core = 8.0.0 -mini.core = 1.0 -ecc.core = 1.0 -vmc.core = 1.0 +mini.core = 2.0 + +;; ecc.core = 1.0 +;; vmc.core = 1.0 ;; Deprecated since Pyarmor 8.2.5 ; cli.runtime = 3.2.5 @@ -149,7 +150,7 @@ enable_rft = 0 ;; Enable opcode virtual protection ;; This feature is meaningless in BCC mode -enable_vmcode = 0 +enable_vmc = 0 ;; assert: call import assert_call = 0 From 61e54978254ea0894482d306197bd21cba6f7b61 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 30 Oct 2025 08:22:36 +0800 Subject: [PATCH 248/337] change core version to 8.1.0 --- setup.py | 2 +- src/cli/core/__init__.py | 2 +- src/cli/default.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 4054ea55..e5bbb2f4 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,7 @@ }, install_requires=[ - 'pyarmor.cli.core~=8.0.0' + 'pyarmor.cli.core~=8.1.0' ], entry_points={ diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index 01f939b3..144b2bbc 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -20,7 +20,7 @@ # @Create Date: Thu Jan 12 17:29:25 CST 2023 # -__VERSION__ = '8.0.0' +__VERSION__ = '8.1.0' PLATFORM_NAMES = ( 'windows.x86_64', 'windows.x86', 'cygwin.x86_64', diff --git a/src/cli/default.cfg b/src/cli/default.cfg index cb43499a..b05549a7 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -6,7 +6,7 @@ minor = 2 patch = 0 ;; Compatible core version -cli.core = 8.0.0 +cli.core = 8.1.0 mini.core = 2.0 ;; ecc.core = 1.0 From a99ef00083fb182676ffc4015e847c111312d319 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 30 Oct 2025 08:53:08 +0800 Subject: [PATCH 249/337] Add upgrade note for Pyarmor 9.2 --- docs/how-to/register.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index b2dce51c..bb273168 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -496,6 +496,32 @@ But in the following versions something is changed It need generate device regfile for each offline device again by Pyarmor 9.0+, refer to :ref:`upgrade to pyarmor 9` +- **Pyarmor 9.2** + + - :term:`Pyarmor CI` + + It need request new ci regfile for new version. The old one still works for old version. + + For example, you have one old ci regfile `pyarmor-ci-8000.zip`, use it for old version. + + After upgrade to Pyarmor 9.2+, request new one `pyarmor-ci-8000.zip`:: + + $ pyarmor reg -C pyarmor-regfile-8000.zip + + Then use new one in Pyarmor 9.2+, note that it doesn't work in any prior to versions. + + - :term:`Pyarmor Group` + + It need update device regfile for new version. The old one still works for old version. + + For example, you have old device regfile ``pyarmor-device-regfile-6000.1.zip`` for device no. 1, use it for old version. + + After upgrade to Pyarmor 9.2+, request new one:: + + $ pyarmor reg -g 1 /path/to/pyarmor-regfile-6000.zip + + Then use new one in Pyarmor 9.2+, note that it doesn't work in any prior to versions. + .. _upgrading old license: Upgrading old license From 71758b94e8f52dcea40d031055f6c9be39cef516 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 30 Oct 2025 10:48:26 +0800 Subject: [PATCH 250/337] Add new obfuscation options --- src/cli/default.cfg | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index b05549a7..162c849c 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -148,10 +148,16 @@ enable_jit = 0 enable_bcc = 0 enable_rft = 0 +;; New in v9.2.0 ;; Enable opcode virtual protection ;; This feature is meaningless in BCC mode enable_vmc = 0 +;; New in v9.2.0 +;; Enable to obfuscate attribute name +;; When obf_mode == 2, it's enabled implicitly +mix_attr = 0 + ;; assert: call import assert_call = 0 assert_import = 0 @@ -170,7 +176,22 @@ mix_localnames = 1 ;; mix argument names, it also clears annotations mix_argnames = 0 +;; Obfuscate the whole module obf_module = 1 + +;; Obfuscate each code object +;; +;; 0: no obfuscation for each co +;; 1: obfuscate each co +;; 2: obfuscate each co plus mix attribute name +;; this is deprecated since v9.2.0, use +;; obf_code == 1 and mix_attr = 1 +;; +;; The following values are introduced since v9.2.0 +;; +;; 3: new mode to obfuscate co +;; 4: more security mode to obfuscate co +;; obf_code = 1 ;; 0: no wrap mode From 36d280e51b422b971466a7b5b89ca4f857573110 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 30 Oct 2025 17:02:52 +0800 Subject: [PATCH 251/337] Refine doc --- docs/how-to/register.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index bb273168..273379a3 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -498,6 +498,8 @@ But in the following versions something is changed - **Pyarmor 9.2** + From Pyarmor 9.0+ upgrade to 9.2, something is changed for :term:`Pyarmor CI` and :term:`Pyarmor Group`. If Pyarmor version < 9.0, please also check the changes in above **Pyarmor 9.0**. + - :term:`Pyarmor CI` It need request new ci regfile for new version. The old one still works for old version. From fa2d9d044c50fc591872a90b8f07b1258f872d34 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 30 Oct 2025 17:24:14 +0800 Subject: [PATCH 252/337] Refine config options --- src/cli/default.cfg | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 162c849c..db39caf3 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -155,7 +155,7 @@ enable_vmc = 0 ;; New in v9.2.0 ;; Enable to obfuscate attribute name -;; When obf_mode == 2, it's enabled implicitly +;; When obf_code == 2, it's enabled implicitly mix_attr = 0 ;; assert: call import @@ -184,13 +184,12 @@ obf_module = 1 ;; 0: no obfuscation for each co ;; 1: obfuscate each co ;; 2: obfuscate each co plus mix attribute name -;; this is deprecated since v9.2.0, use +;; this is deprecated since 9.2.0, use ;; obf_code == 1 and mix_attr = 1 ;; -;; The following values are introduced since v9.2.0 +;; The following values may be introduced in v9.2 ;; -;; 3: new mode to obfuscate co -;; 4: more security mode to obfuscate co +;; 3: more security mode to obfuscate co ;; obf_code = 1 From bebb51e9695f66b8693e20429d9cb51abef20414 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 30 Oct 2025 19:43:04 +0800 Subject: [PATCH 253/337] Refine config option --- src/cli/default.cfg | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index db39caf3..0d8d0524 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -148,7 +148,7 @@ enable_jit = 0 enable_bcc = 0 enable_rft = 0 -;; New in v9.2.0 +;; Not implemented ;; Enable opcode virtual protection ;; This feature is meaningless in BCC mode enable_vmc = 0 @@ -186,11 +186,6 @@ obf_module = 1 ;; 2: obfuscate each co plus mix attribute name ;; this is deprecated since 9.2.0, use ;; obf_code == 1 and mix_attr = 1 -;; -;; The following values may be introduced in v9.2 -;; -;; 3: more security mode to obfuscate co -;; obf_code = 1 ;; 0: no wrap mode From 4cbbbd161ee4dddf4db870a0e14296a5131ff43b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 30 Oct 2025 20:30:37 +0800 Subject: [PATCH 254/337] Update release plan --- docs/ReleasePlan.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md index 81492bab..43a71f90 100755 --- a/docs/ReleasePlan.md +++ b/docs/ReleasePlan.md @@ -78,22 +78,22 @@ Status: released (2024-11-10) **9.1** -Status: developing +Status: released (2025-02-24) -- Full testcases -- Refine RFT mode to improve performance for big scripts (same as MINI mode) - MINI mode, a simple RFT mode, the goal is high performance after obfuscation **9.2** -Status: developing, released on 2025-12-01, 2 months after Python 3.14 is released +Status: released (2025-10-31) -- Support Python 3.14 which will be released on 2025-10-01 as plan +- Support Python 3.14 and 3.15 (a2) **9.3** -Status: not start +Status: developing +- Full testcases +- Refine RFT mode to improve performance for big scripts (same as MINI mode) - ECC mode, mini BCC mode, only transform part of function body to C code - VMC mode, simple ECC mode, convert function body to VM, not real C code From 7649e427a3f8f2d11aa7ca3d22938add3cced157 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 30 Oct 2025 22:45:01 +0800 Subject: [PATCH 255/337] Fix LICENSE_REVSION issue --- src/cli/register.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index ebed17c7..e314e2e9 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -480,6 +480,10 @@ def __str__(self): class WebRegister(Register): + # Pyarmor v9.0: 2 + # Pyarmor v9.2: 3 + LICENSE_REVSION = 3 + def _request(self, url): from http.client import HTTPSConnection n = len('https://') @@ -635,8 +639,7 @@ def register(self, keyfile, product, upgrade=False, group=False): reginfo = self.parse_keyfile(keyfile) url = self.regurl(reginfo[1], product=product) - # Request license file with extra info by rev 2 - url += '&rev=2' + url += '&rev=' + str(self.LICENSE_REVSION) if upgrade: url += '&upgrade_to_basic=1' logger.debug('url: %s', url) @@ -727,10 +730,11 @@ def _write_group_info(self, filename, data): with ZipFile(filename, 'a') as f: f.writestr('group.info', data) - def register_group_device(self, regfile, devid, rev=2): + def register_group_device(self, regfile, devid): from zipfile import ZipFile devfile = self.ctx.group_device_file(devid) - logger.info('register device file "%s"', devfile) + rev = self.LICENSE_REVSION + logger.info('register device file "%s" (v%d)', devfile, rev) logger.info('use group license "%s"', regfile) if not os.path.exists(devfile): logger.error('please generate device file in offline device by') @@ -796,8 +800,9 @@ def _write_ci_info(self, filename, data): with ZipFile(filename, 'a') as f: f.writestr('ci.token', data) - def request_ci_regfile(self, regfile, rev=2): - logger.info('request ci regfile by "%s"', regfile) + def request_ci_regfile(self, regfile): + rev = self.LICENSE_REVSION + logger.info('request ci regfile v%d by "%s"', rev, regfile) from zipfile import ZipFile with ZipFile(regfile, 'r') as f: From 710cad313e0cc54d985135e59fe271e7a0d8be1d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 31 Oct 2025 11:19:45 +0800 Subject: [PATCH 256/337] Add helper to activate different licenses --- src/cli/register.py | 240 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 216 insertions(+), 24 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index e314e2e9..0d140f3c 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -89,7 +89,7 @@ def parse_token(data): def show_help_page(prompt, url): choice = input('\n'.join(prompt)).lower()[:1] - if choice == 'h': + if choice in ('h', 'y'): import webbrowser webbrowser.open(url) return choice @@ -619,7 +619,7 @@ def upgrade_to_pro(self, keyfile, product): logger.info('send upgrade request to server') res = self._send_request(url) - regfile = self._handle_response(res) + regfile, lictype = self._handle_response(res) logger.info('update license token') self.update_token() @@ -646,7 +646,7 @@ def register(self, keyfile, product, upgrade=False, group=False): logger.info('send request to server') res = self._send_request(url) - regfile = self._handle_response(res) + regfile, lictype = self._handle_response(res) logger.info('') logger.info('the registration file "%s" has been generated', regfile) @@ -658,35 +658,21 @@ def register(self, keyfile, product, upgrade=False, group=False): '* Please backup regfile "%s", and ' 'use this file for next any registration' % regfile, '* Do not use this file in docker and CI/CD pipeline', + '', ] - - if group: - notes.append('* Please check `pyarmor reg` in Man page for ' - 'how to register Pyarmor on offline device') - logger.info('\n\nImport Notes:\n%s\n', '\n'.join(notes)) - return - - notes.append('') - notes.append('Next register Pyarmor in build device by this command:') - - notes.append('\tpyarmor reg %s' % regfile) - notes.append('') logger.info('\n\nImport Notes:\n%s\n', '\n'.join(notes)) - prompt = 'Do you want register Pyarmor in this machine? (Y/n) ' - choice = input(prompt).lower()[:1] - if choice == 'y': - logger.info('register "%s"', regfile) - self.register_regfile(regfile) - logger.info('This license registration information:\n\n%s', self) + LicenseHelper(self).run(lictype, regfile) def _handle_response(self, res): if res and res.code == 200: + lictype = None dis = res.headers.get('Content-Disposition') filename = dis.split('"')[1] if dis else 'pyarmor-regfile.zip' logger.info('write registration file "%s"', filename) data = res.read() if data.startswith(b'{"group":'): + lictype = 'G' n = data.find(b'}') + 1 with open(filename, 'wb') as f: f.write(data[n:]) @@ -698,13 +684,15 @@ def _handle_response(self, res): n += i with open(filename, 'wb') as f: f.write(data[n:]) - self._write_reg_info(filename, data[i:n]) + reginfo = data[i:n] + lictype = json_loads(reginfo).get('type', None) + self._write_reg_info(filename, reginfo) else: # Only for request group token logger.debug('no REGINFO found') with open(filename, 'wb') as f: f.write(data) - return filename + return filename, lictype elif res: data = res.read() @@ -778,7 +766,7 @@ def register_group_device(self, regfile, devid): logger.debug('url: %s', url) res = self._send_request(url) - filename = self._handle_response(res) + filename, lictype = self._handle_response(res) with open(filename, 'rb') as f: data = f.read() os.makedirs(os.path.dirname(tokencache), exist_ok=True) @@ -859,3 +847,207 @@ def request_ci_regfile(self, regfile): '\n\tpip install pyarmor==%s\n' '\tpyarmor reg %s\n', cifile, ver, cifile) + + +BASIC_LICENSE_HELP_INFO = Template(''' +Using Basic license in CI/CD pipeline or docker container need extra steps, please check this page + +$docurl/how-to/ci.html + +More usage about Basic License, check this page + +$docurl/how-to/register.html + +If need register Pyarmor in build device, run this command: + + pyarmor reg $regfile + +''') + +PRO_LICENSE_HELP_INFO = Template(''' +Pro license can't be used in CI/CD pipeline or docker container direclty. + +A few times for debug purpose, about 60 runs per month, may work. + +But there is one workaroud to use Pro licnese in CI/CD pipeline, please check this page + +$docurl/how-to/ci.html + +More usage about Pro License, check this page + +$docurl/how-to/register.html + +If need register Pyarmor in build device, run this command: + + pyarmor reg $regfile + +''') + +GROUP_LICENSE_HELP_INFO = Template(''' +Group License file `$regfile` is only used to request device regfile + +In order to register Pyarmor in offline device: + +1. In the build device (may be offline), generate device info by this command + + pyarmor reg -g 1 + +2. In any online device, copy device file generated by first step, then request device regfile for this device + + $ cp pyarmor-group-device.1 .pyarmor/group/ + $ pyarmor reg -g 1 $regfile + +3. In the build device, register Pyarmor by device regfile + + $ pyarmor reg pyarmor-device-regfile-xxxx.1.zip + +More usage about CI License, check section `Using group licnese` +$docurl/how-to/register.html +''') + +CI_LICENSE_HELP_INFO = Template(''' +fCI license file `$regfile` is only used to request CI regfile + +CI regfile is used to register Pyarmor in the CI/CD pipeline + +If need request CI regfile, run this command: + + pyarmor reg -C $regfile + +Note that CI regfile can NOT be used in physical machine, and there is rate limits to register CI regfile in CI/CD pipeline and docker container. + +It may need request new CI regfile after Pyarmor is upgraded, please check section `When need to request new CI regfile` in this page +$docurl/how-to/ci.html + +More usage about CI License, check these pages + +$docurl/how-to/register.html +$docurl/how-to/ci.html +''') + + +class LicenseHelper(object): + """Only used for first activate license. + + Help the beginner to understand how to use different licenses. + + """ + + def __init__(self, parent): + self.parent = parent + self.docurl = parent.ctx.cfg.get('pyarmor', 'docurl').rstrip('/') + + def run(self, lictype, regfile): + if lictype == 'G': + self._group_license_helper(regfile) + + elif lictype == 'C': + self._ci_license_helper(regfile) + + elif lictype == 'Z': + self._pro_license_helper(regfile) + + elif lictype in ('J', 'B', 'P'): + self._basic_license_helper(regfile) + + else: + raise CliError('unknown license type "%s"' % lictype) + + def _basic_license_helper(self, regfile): + logger.info(BASIC_LICENSE_HELP_INFO.format( + docurl=self.docurl, regfile=regfile)) + + prompt = 'Yes (y), No (n), Quit (q): ' + logger.info('Show basic license usage in webbrowser? (n)') + choice = show_help_page(prompt, self.docurl + '/how-to/register.html') + if choice == 'q': + return + + logger.info('Show basic license for CI/CD pipeline or docker? (n)') + choice = show_help_page(prompt, self.docurl + '/how-to/ci.html') + if choice == 'q': + return + + logger.info('Do you want register Pyarmor in this machine? (y)') + choice = input(prompt).lower()[:1] + if choice == 'q': + return + + if choice in ('y', ''): + logger.info('register "%s"', regfile) + self.parent.register_regfile(regfile) + logger.info('This license registration information:\n\n%s', + self.parent) + + def _pro_license_helper(self, regfile): + logger.info(PRO_LICENSE_HELP_INFO.format( + docurl=self.docurl, regfile=regfile)) + + prompt = 'Yes (y), No (n), Quit (q): ' + logger.info('Show pro license usage in webbrowser? (n)') + choice = show_help_page(prompt, self.docurl + '/how-to/register.html') + if choice == 'q': + return + + logger.info('Show pro license for CI/CD pipeline or docker? (n)') + choice = show_help_page(prompt, self.docurl + '/how-to/ci.html') + if choice == 'q': + return + + logger.info('Do you want register Pyarmor in this machine? (y)') + choice = input(prompt).lower()[:1] + if choice == 'q': + return + + if choice in ('y', ''): + logger.info('register "%s"', regfile) + self.parent.register_regfile(regfile) + logger.info('This license registration information:\n\n%s', + self.parent) + + def _group_license_helper(self, regfile): + logger.info(GROUP_LICENSE_HELP_INFO.format( + docurl=self.docurl, regfile=regfile)) + + prompt = 'Yes (y), No (n), Quit (q): ' + logger.info('Show group license usage in webbrowser? (n)') + choice = show_help_page(prompt, self.docurl + '/how-to/register.html') + if choice == 'q': + return + + logger.info('Do you want register Pyarmor in this machine? (y)') + choice = input(prompt).lower()[:1] + if choice == 'q': + return + + if choice in ('y', ''): + devid = 1 + sep = '-' * 16 + devinfo = self.ctx.group_device_file(devid) + logger.info('%s 1. generate device info', sep) + if not os.path.exists(devinfo): + self.parent.generate_group_device(devid) + logger.info('%s 2. request device regfile', sep) + self.parent.register_group_device(regfile, devid) + logger.info('%s 3. register Pyarmor with device regfile', sep) + self.parent.register(regfile. + replace('.zip', '.%d.zip' % devid). + replace('-regfile', '-device-regfile')) + + def _ci_license_helper(self, regfile): + logger.info(CI_LICENSE_HELP_INFO.format( + docurl=self.docurl, regfile=regfile)) + + prompt = 'Yes (y), No (n), Quit (q): ' + logger.info('Show ci license usage in webbrowser? (n)') + choice = show_help_page(prompt, self.docurl + '/how-to/ci.html') + if choice == 'q': + return + + logger.info('Do you want request one CI Regfile now? (y)') + choice = input(prompt).lower()[:1] + if choice == 'q': + return + + if choice in ('y', ''): + self.parent.request_ci_regfile(regfile) From 8c792d5e2f4603cc94879ed13cb83dd3913072c8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 31 Oct 2025 13:02:25 +0800 Subject: [PATCH 257/337] Refine code --- src/cli/__main__.py | 2 +- src/cli/register.py | 112 +++++++++++++++++++++++++++----------------- 2 files changed, 69 insertions(+), 45 deletions(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index ae4a9e6e..11754268 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -309,7 +309,7 @@ def cmd_reg(ctx, args): raise CliError('please use ".zip" file to register group device') regsvr = WebRegister(ctx) regsvr.check_request_interval() - regsvr.register_group_device(regfile, args.device) + regsvr.request_device_regfile(regfile, args.device) logger.info('The device regfile has been generated successfully') elif args.ci: diff --git a/src/cli/register.py b/src/cli/register.py index 0d140f3c..388c37c1 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -503,8 +503,9 @@ def check_request_interval(self, delta=30.0): from time import sleep, time d = delta - (time() - st.st_mtime) if d > 0: - logger.info('last register was within %d seconds, ' - 'waiting for %d seconds', delta, d) + logger.warning('caution: this activation file can only ' + 'be used no more than 10 times') + logger.info('waiting for %d seconds', d) while time() - st.st_mtime < delta: logger.info('waiting ...') sleep(3.0) @@ -662,6 +663,7 @@ def register(self, keyfile, product, upgrade=False, group=False): ] logger.info('\n\nImport Notes:\n%s\n', '\n'.join(notes)) + input('Type Enter to continue ...') LicenseHelper(self).run(lictype, regfile) def _handle_response(self, res): @@ -718,11 +720,11 @@ def _write_group_info(self, filename, data): with ZipFile(filename, 'a') as f: f.writestr('group.info', data) - def register_group_device(self, regfile, devid): + def request_device_regfile(self, regfile, devid): from zipfile import ZipFile devfile = self.ctx.group_device_file(devid) rev = self.LICENSE_REVSION - logger.info('register device file "%s" (v%d)', devfile, rev) + logger.info('request device regfile "%s" (v%d)', devfile, rev) logger.info('use group license "%s"', regfile) if not os.path.exists(devfile): logger.error('please generate device file in offline device by') @@ -782,6 +784,8 @@ def register_group_device(self, regfile, devid): logger.info('please copy deivce regfile to offline device and run') logger.info(' pyarmor reg %s', filename) + register_group_device = request_device_regfile + def _write_ci_info(self, filename, data): from zipfile import ZipFile logger.info('write ci information') @@ -869,7 +873,7 @@ def request_ci_regfile(self, regfile): A few times for debug purpose, about 60 runs per month, may work. -But there is one workaroud to use Pro licnese in CI/CD pipeline, please check this page +But there is one workaroud for Pro licnese in CI/CD pipeline, please check this page $docurl/how-to/ci.html @@ -894,19 +898,20 @@ def request_ci_regfile(self, regfile): 2. In any online device, copy device file generated by first step, then request device regfile for this device - $ cp pyarmor-group-device.1 .pyarmor/group/ - $ pyarmor reg -g 1 $regfile + cp pyarmor-group-device.1 .pyarmor/group/ + pyarmor reg -g 1 $regfile 3. In the build device, register Pyarmor by device regfile - $ pyarmor reg pyarmor-device-regfile-xxxx.1.zip + pyarmor reg pyarmor-device-regfile-xxxx.1.zip More usage about CI License, check section `Using group licnese` + $docurl/how-to/register.html ''') CI_LICENSE_HELP_INFO = Template(''' -fCI license file `$regfile` is only used to request CI regfile +CI license file `$regfile` is only used to request CI regfile CI regfile is used to register Pyarmor in the CI/CD pipeline @@ -917,6 +922,7 @@ def request_ci_regfile(self, regfile): Note that CI regfile can NOT be used in physical machine, and there is rate limits to register CI regfile in CI/CD pipeline and docker container. It may need request new CI regfile after Pyarmor is upgraded, please check section `When need to request new CI regfile` in this page + $docurl/how-to/ci.html More usage about CI License, check these pages @@ -935,7 +941,9 @@ class LicenseHelper(object): def __init__(self, parent): self.parent = parent - self.docurl = parent.ctx.cfg.get('pyarmor', 'docurl').rstrip('/') + self.docurl = parent.ctx.cfg.get( + 'pyarmor', 'docurl').rstrip('/').replace('{lang}', 'en') + self.print = print def run(self, lictype, regfile): if lictype == 'G': @@ -954,97 +962,113 @@ def run(self, lictype, regfile): raise CliError('unknown license type "%s"' % lictype) def _basic_license_helper(self, regfile): - logger.info(BASIC_LICENSE_HELP_INFO.format( + self.print(BASIC_LICENSE_HELP_INFO.substitute( docurl=self.docurl, regfile=regfile)) prompt = 'Yes (y), No (n), Quit (q): ' - logger.info('Show basic license usage in webbrowser? (n)') - choice = show_help_page(prompt, self.docurl + '/how-to/register.html') + self.print('Show basic license usage in webbrowser? (n)') + choice = show_help_page([prompt], self.docurl + '/how-to/register.html') if choice == 'q': return - logger.info('Show basic license for CI/CD pipeline or docker? (n)') - choice = show_help_page(prompt, self.docurl + '/how-to/ci.html') + self.print('Show basic license for CI/CD pipeline or docker? (n)') + choice = show_help_page([prompt], self.docurl + '/how-to/ci.html') if choice == 'q': return - logger.info('Do you want register Pyarmor in this machine? (y)') + self.print('Do you want register Pyarmor in this machine? (y)') choice = input(prompt).lower()[:1] if choice == 'q': return if choice in ('y', ''): - logger.info('register "%s"', regfile) + self.print('register "%s"' % regfile) self.parent.register_regfile(regfile) - logger.info('This license registration information:\n\n%s', - self.parent) + self.print('This license registration information:\n\n' + '%s' % self.parent) def _pro_license_helper(self, regfile): - logger.info(PRO_LICENSE_HELP_INFO.format( + self.print(PRO_LICENSE_HELP_INFO.substitute( docurl=self.docurl, regfile=regfile)) prompt = 'Yes (y), No (n), Quit (q): ' - logger.info('Show pro license usage in webbrowser? (n)') - choice = show_help_page(prompt, self.docurl + '/how-to/register.html') + self.print('Show pro license usage in webbrowser? (n)') + choice = show_help_page([prompt], self.docurl + '/how-to/register.html') if choice == 'q': return - logger.info('Show pro license for CI/CD pipeline or docker? (n)') - choice = show_help_page(prompt, self.docurl + '/how-to/ci.html') + self.print('Show pro license for CI/CD pipeline or docker? (n)') + choice = show_help_page([prompt], self.docurl + '/how-to/ci.html') if choice == 'q': return - logger.info('Do you want register Pyarmor in this machine? (y)') + self.print('Do you want register Pyarmor in this machine? (y)') choice = input(prompt).lower()[:1] if choice == 'q': return if choice in ('y', ''): - logger.info('register "%s"', regfile) + self.print('register "%s"' % regfile) self.parent.register_regfile(regfile) - logger.info('This license registration information:\n\n%s', - self.parent) + self.print('This license registration information:\n\n' + '%s' % self.parent) def _group_license_helper(self, regfile): - logger.info(GROUP_LICENSE_HELP_INFO.format( + self.print(GROUP_LICENSE_HELP_INFO.substitute( docurl=self.docurl, regfile=regfile)) prompt = 'Yes (y), No (n), Quit (q): ' - logger.info('Show group license usage in webbrowser? (n)') - choice = show_help_page(prompt, self.docurl + '/how-to/register.html') + self.print('Show group license usage in webbrowser? (n)') + choice = show_help_page([prompt], self.docurl + '/how-to/register.html') if choice == 'q': return - logger.info('Do you want register Pyarmor in this machine? (y)') + self.print('Do you want register Pyarmor in this machine? (n)') choice = input(prompt).lower()[:1] if choice == 'q': return - if choice in ('y', ''): - devid = 1 + if choice != 'y': + return + + self.print('Please assign one unused device no. to this device, ' + 'starts from 1') + devid = None + while devid is None: + a = input('Type device no. (default is 1): ') + if a == '': + devid = 1 + elif (not a.isdigit()) or int(a) < 1 or int(a) > 100: + self.print('invalid input') + else: + devid = int(a) + + if devid: sep = '-' * 16 - devinfo = self.ctx.group_device_file(devid) - logger.info('%s 1. generate device info', sep) - if not os.path.exists(devinfo): + devinfo = self.parent.ctx.group_device_file(devid) + self.print('%s 1. generate device info' % sep) + if os.path.exists(devinfo): + logger.warning('old device file has been exists') + else: self.parent.generate_group_device(devid) - logger.info('%s 2. request device regfile', sep) - self.parent.register_group_device(regfile, devid) - logger.info('%s 3. register Pyarmor with device regfile', sep) + self.print('%s 2. request device regfile' % sep) + self.parent.request_device_regfile(regfile, devid) + self.print('%s 3. register Pyarmor with device regfile' % sep) self.parent.register(regfile. replace('.zip', '.%d.zip' % devid). replace('-regfile', '-device-regfile')) def _ci_license_helper(self, regfile): - logger.info(CI_LICENSE_HELP_INFO.format( + self.print(CI_LICENSE_HELP_INFO.substitute( docurl=self.docurl, regfile=regfile)) prompt = 'Yes (y), No (n), Quit (q): ' - logger.info('Show ci license usage in webbrowser? (n)') - choice = show_help_page(prompt, self.docurl + '/how-to/ci.html') + self.print('Show ci license usage in webbrowser? (n)') + choice = show_help_page([prompt], self.docurl + '/how-to/ci.html') if choice == 'q': return - logger.info('Do you want request one CI Regfile now? (y)') + self.print('Do you want request one CI Regfile now? (y)') choice = input(prompt).lower()[:1] if choice == 'q': return From dc6b15fc3997b653fdcab707939f9ae8e1f6b9e2 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 31 Oct 2025 17:06:21 +0800 Subject: [PATCH 258/337] Add para ci_rev when request ci regfile --- src/cli/register.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index 388c37c1..2770668d 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -480,10 +480,16 @@ def __str__(self): class WebRegister(Register): + # Before v9.0: 1 # Pyarmor v9.0: 2 # Pyarmor v9.2: 3 LICENSE_REVSION = 3 + # Before v9.0: no CI License + # Pyarmor v9.0: 1 + # Pyarmor v9.2: 2 + CI_LICENSE_REVSION = 2 + def _request(self, url): from http.client import HTTPSConnection n = len('https://') @@ -794,7 +800,8 @@ def _write_ci_info(self, filename, data): def request_ci_regfile(self, regfile): rev = self.LICENSE_REVSION - logger.info('request ci regfile v%d by "%s"', rev, regfile) + ci_rev = self.CI_LICENSE_REVSION + logger.info('request ci regfile (v%d) by "%s"', ci_rev, regfile) from zipfile import ZipFile with ZipFile(regfile, 'r') as f: @@ -811,7 +818,7 @@ def request_ci_regfile(self, regfile): raise CliError('invalid registration file "%s"', regfile) url = self.regurl('ci/%s' % ucode) - paras = (('rev', str(rev)),) + paras = ('rev', str(rev)), ('cirev', str(ci_rev)) url += '&'.join(['='.join(x) for x in paras]) logger.debug('url: %s', url) From 2f77e1293a86e8306c48ab4bdc6444c418ad264e Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 31 Oct 2025 18:01:21 +0800 Subject: [PATCH 259/337] Refine code --- src/cli/register.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/register.py b/src/cli/register.py index 2770668d..a9e16238 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -800,8 +800,8 @@ def _write_ci_info(self, filename, data): def request_ci_regfile(self, regfile): rev = self.LICENSE_REVSION - ci_rev = self.CI_LICENSE_REVSION - logger.info('request ci regfile (v%d) by "%s"', ci_rev, regfile) + cirev = self.CI_LICENSE_REVSION + logger.info('request ci regfile (v%d) by "%s"', cirev, regfile) from zipfile import ZipFile with ZipFile(regfile, 'r') as f: @@ -818,7 +818,7 @@ def request_ci_regfile(self, regfile): raise CliError('invalid registration file "%s"', regfile) url = self.regurl('ci/%s' % ucode) - paras = ('rev', str(rev)), ('cirev', str(ci_rev)) + paras = ('rev', str(rev)), ('cirev', str(cirev)) url += '&'.join(['='.join(x) for x in paras]) logger.debug('url: %s', url) From c85fc078abcaca78cd8bd99a59c62a7dca728c22 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 31 Oct 2025 18:31:48 +0800 Subject: [PATCH 260/337] Doc support for Python 3.14 and 3.15 --- docs/reference/environments.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/reference/environments.rst b/docs/reference/environments.rst index 7e67cac6..71489193 100644 --- a/docs/reference/environments.rst +++ b/docs/reference/environments.rst @@ -20,14 +20,14 @@ Supported Python versions .. table:: Table-1. Supported Python Versions :widths: auto - =================== ===== ========= ========== ====== ====== ======= ============== - Python Version 2.7 3.0~3.6 3.7~3.10 3.11 3.12 3.13 Remark - =================== ===== ========= ========== ====== ====== ======= ============== - pyarmor 8 RFT Mode No No Y Y Y Y [#]_ - pyarmor 8 BCC Mode No No Y Y Y Y - pyarmor 8 others No No Y Y Y Y - pyarmor-7 Y Y Y No No No - =================== ===== ========= ========== ====== ====== ======= ============== + =================== ===== ========= ========== ============ ============== + Python Version 2.7 3.0~3.6 3.7~3.10 3.11~3.15 Remark + =================== ===== ========= ========== ============ ============== + pyarmor 8 RFT Mode No No Y Y [#]_ + pyarmor 8 BCC Mode No No Y Y + pyarmor 8 others No No Y Y + pyarmor-7 Y Y Y No + =================== ===== ========= ========== ============ ============== Supported platforms =================== From 6e89784c10d7fc29cfbfb5681a8b0a91514a85bc Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 2 Nov 2025 12:35:05 +0800 Subject: [PATCH 261/337] Change version to 9.2.1 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index ba255ae1..629cb5ad 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.2' # The full version, including alpha/beta/rc tags. -release = '9.2.0' +release = '9.2.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 70421870..df8e0cf9 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.2.0' +__VERSION__ = '9.2.1' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 0d8d0524..93f063ba 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 2 -patch = 0 +patch = 1 ;; Compatible core version cli.core = 8.1.0 diff --git a/src/config.py b/src/config.py index dfc6b034..f0f7ce25 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.2.0' +version = '9.2.1' # The corresponding version of pytransform.so core_version = 'r52.6' From 7c395f7dcbdcd47c734737442391219d5b989395 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 4 Nov 2025 16:37:44 +0800 Subject: [PATCH 262/337] Only show no more than 10 times caution for activating license --- src/cli/__main__.py | 2 +- src/cli/register.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 11754268..1b132a8b 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -325,7 +325,7 @@ def cmd_reg(ctx, args): else: regsvr = WebRegister(ctx) - regsvr.check_request_interval() + regsvr.check_request_interval(activation=regfile) info, msg = regsvr.prepare(regfile, args.product, upgrade=upgrade) prompt = 'Are you sure to continue? (yes/no) ' if args.confirm: diff --git a/src/cli/register.py b/src/cli/register.py index a9e16238..a7fcdea8 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -498,7 +498,7 @@ def _request(self, url): conn.request("GET", url[k:]) return conn.getresponse() - def check_request_interval(self, delta=30.0): + def check_request_interval(self, delta=30.0, activation=False): """Make sure no more than 2 requests in 1 minute""" tspath = os.path.join(self.ctx.reg_path, 'last_register') try: @@ -509,8 +509,10 @@ def check_request_interval(self, delta=30.0): from time import sleep, time d = delta - (time() - st.st_mtime) if d > 0: - logger.warning('caution: this activation file can only ' - 'be used no more than 10 times') + if activation: + logger.warning( + 'caution: this activation file "%s" can only ' + 'be used no more than 10 times', activation) logger.info('waiting for %d seconds', d) while time() - st.st_mtime < delta: logger.info('waiting ...') From 4e4e3be2696cc5a795ecfe7f6aa1edbf4f3a70ed Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 5 Nov 2025 20:22:51 +0800 Subject: [PATCH 263/337] Refine doc --- docs/how-to/ci.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 07558531..23eecd55 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -61,7 +61,7 @@ Notes .. important:: - In CI/CD pipeline, each run will send license and docker information to Pyarmor License Server, excessive requests or requests beyond normal usage may be rejected by Pyarmor License Server. Generally do not exceed any of these rate limits: + In CI/CD pipeline, each run `pyarmor gen` will send license and docker information to Pyarmor License Server, excessive requests or requests beyond normal usage may be rejected by Pyarmor License Server. Generally do not exceed any of these rate limits: - 1 run per second - 100 runs per hour @@ -70,7 +70,9 @@ Notes If exceeds any of these limitions, please check the section `High frequency use solution` - It's not allowed to install Pyarmor in your customer's docker image +.. important:: + + It's not allowed to install and register Pyarmor in your customer's docker image, Pyarmor CI license is only be used in the build device. When need to request new CI regfile ----------------------------------- From 35e63bba9668862b5cd71db39f2ef92861db5b3b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 5 Nov 2025 21:07:54 +0800 Subject: [PATCH 264/337] Refine doc --- docs/how-to/ci.rst | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 23eecd55..6782af36 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -87,13 +87,51 @@ High frequency use solution .. versionadded:: 9.2.0 -For daily build, it need special workflow. +If many `pyarmor gen` commands are used in one workflow, try to merge them to one pyarmor process -Pyarmor team need verify the project, and understand how many runs per hour and per month probably. +For example, create one script `batch.py` like these: -Generally, it should be no more than 1,000 runs per hour. +.. code-block:: python -Each month the free quota is 10, 000 runs, if need more quota, it need extra fees: + import shlex + import os + + from pyarmor.cli.__main__ import main_entry as pyarmor_run + + # Do not run `pyarmor reg pyarmor-ci-xxxx.zip` here, run it before this script + + build_commands = """ + pyarmor gen main.py + cd ../package1 + pyarmor gen -R --enable-bcc src/ + cd ../package2 + pyarmor gen -R --enable-jit --private src/ + """ + + for cmd in build_commands.splitlines(): + if cmd.startswith('#'): + continue + + if cmd.startswith('cd '): + path = cmd[3:].strip() + print('Change current path:', path) + os.chdir(path) + + elif cmd.startswith('pyarmor '): + print('Execute: ', cmd) + args = shlex.split(cmd[8:].strip()) + pyarmor_run(args) + +Then call it in the workflow:: + + $ pyarmor reg pyarmor-ci-8000.zip + $ python3 batch.py + +If merge solution doesn't work, or you don't want change the original workflow, it need extra steps. + +Pyarmor team need verify the project, and use special server to verify this license. Even that, it should be no more than 1,000 runs per hour, and need extra fees. + +The free quota is 10, 000 runs per month, exceed that: - 100,000 per month, extra fee: $10 for one year - 200,000 per month, extra fee: $20 for one year From fff39883840396a5303635e922a696e5dcdf6fea Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 5 Nov 2025 21:25:39 +0800 Subject: [PATCH 265/337] Refine doc --- docs/how-to/ci.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 6782af36..155ce224 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -80,7 +80,7 @@ When need to request new CI regfile In the following cases, it need request one new CI regfile - After :term:`Pyarmor CI` License is expired, all the previous CI regfiles don't work any longer. After the renewal is successful, it need request new CI regfile ``pyarmor-ci-N.zip`` -- After Pyarmor is upgrade one new major/minor version, the old CI regfile may not work in the latest version (but it still works with old Pyarmor version). It need request one new CI regfile by the version. Note that the patch number has no effect for this case, for example, from v9.1.3 to v9.1.8, nothing changed. +- After Pyarmor is upgrade one new major/minor version, the old CI regfile may not work in the latest version (but it still works with old Pyarmor version). It need request one new CI regfile by new version. Note that the patch number has no effect for this case, for example, from v9.1.3 to v9.1.8, nothing changed. High frequency use solution --------------------------- @@ -129,7 +129,7 @@ Then call it in the workflow:: If merge solution doesn't work, or you don't want change the original workflow, it need extra steps. -Pyarmor team need verify the project, and use special server to verify this license. Even that, it should be no more than 1,000 runs per hour, and need extra fees. +Pyarmor team need verify the project, and use special license server for this case. The free quota is 10, 000 runs per month, exceed that: From 64dc3da885cb671e40a13556e4679c5d31f5458f Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 6 Nov 2025 07:42:05 +0800 Subject: [PATCH 266/337] Refine sample script --- docs/how-to/ci.rst | 60 ++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 155ce224..483b8c1b 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -87,55 +87,59 @@ High frequency use solution .. versionadded:: 9.2.0 -If many `pyarmor gen` commands are used in one workflow, try to merge them to one pyarmor process +If many `pyarmor gen` commands are used in one workflow, try to merge them to one -For example, create one script `batch.py` like these: +For example:: + + # Old workflow: there are 3 "pyarmor gen" + pyarmor gen -R /path/to/package1 + pyarmor gen -R /path/to/package2 + pyarmor gen -R /path/to/package3 + + # New workflow: merge 3 to one + pyarmor gen -R /path/to/package1 /path/to/package2 /path/to/package2 + +Or create one Python script to execute all pyarmor commands in one process + +For example, create one script `batch.py`: .. code-block:: python - import shlex import os + import shlex from pyarmor.cli.__main__ import main_entry as pyarmor_run - # Do not run `pyarmor reg pyarmor-ci-xxxx.zip` here, run it before this script + # Do not run `pyarmor reg pyarmor-ci-XXXX.zip` in the script - build_commands = """ - pyarmor gen main.py - cd ../package1 - pyarmor gen -R --enable-bcc src/ - cd ../package2 - pyarmor gen -R --enable-jit --private src/ - """ + # Run command: pyarmor gen -R /path/to/package1 + pyarmor_run(['gen', '-R', '/path/to/package1']) - for cmd in build_commands.splitlines(): - if cmd.startswith('#'): - continue + # Or more like shell command to run: pyarmor gen -R /path/to/package2 + cmdlist = shlex.split("pyarmor gen -R /path/to/package2") + pyarmor_run(cmdlist[1:]) - if cmd.startswith('cd '): - path = cmd[3:].strip() - print('Change current path:', path) - os.chdir(path) + # Or change path + os.chdir('/path/to/other') - elif cmd.startswith('pyarmor '): - print('Execute: ', cmd) - args = shlex.split(cmd[8:].strip()) - pyarmor_run(args) + # Execute any other pyarmor command + cmdlist = shlex.split("pyarmor gen key -e 30") + pyarmor_run(cmdlist[1:]) -Then call it in the workflow:: +Then execute it in the workflow:: $ pyarmor reg pyarmor-ci-8000.zip $ python3 batch.py If merge solution doesn't work, or you don't want change the original workflow, it need extra steps. -Pyarmor team need verify the project, and use special license server for this case. +Pyarmor team need verify this project, and you need request one new CI regfile to use special license server for it. -The free quota is 10, 000 runs per month, exceed that: +The free quota is 10, 000 runs per month, exceed free quota, it need extra fees: -- 100,000 per month, extra fee: $10 for one year -- 200,000 per month, extra fee: $20 for one year -- 300,000 per month, extra fee: $30 for one year +- 100,000 per month, extra fees: $10 for one year +- 200,000 per month, extra fees: $20 for one year +- 300,000 per month, extra fees: $30 for one year - ... Indirect Way From e6c7d45e9aff071c5ac31867aa219c42ecce841b Mon Sep 17 00:00:00 2001 From: LY <51789698+Young-Lord@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:46:22 +0800 Subject: [PATCH 267/337] Fix file encoding detection --- src/cli/resource.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/resource.py b/src/cli/resource.py index fade2cb8..6f2a9e44 100644 --- a/src/cli/resource.py +++ b/src/cli/resource.py @@ -124,7 +124,8 @@ def _get_encoding(self, encoding): k = n + 1 n = line.find(b'\n', k) m = research(r'coding[=:]\s*([-\w.]+)', line[k:n].decode()) - return m and m.group(1) + if m: + return m.group(1) return encoding def readlines(self, encoding=None): From c545d1293e33ce5dac0ffc255e65f44c1927e7dc Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 9 Nov 2025 22:52:59 +0800 Subject: [PATCH 268/337] Refine encoding detection --- src/cli/resource.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/cli/resource.py b/src/cli/resource.py index 6f2a9e44..249f6b29 100644 --- a/src/cli/resource.py +++ b/src/cli/resource.py @@ -109,23 +109,34 @@ def is_pyc(self): return self.pyext.lower() == '.pyc' def _get_encoding(self, encoding): + # Maybe: from tokenize import detect_encoding from codecs import BOM_UTF8 from re import search as research + + def find_encoding(line): + try: + pat = r'coding[=:]\s*([-\w.]+)' + n = line.find(b'\n') + if n > -1: + m = research(pat, line[:n].decode()) + if m: + return m.group(1) + if line[n+1] == 35: + k = n + 1 + n = line.find(b'\n', k) + m = research(pat, line[k:n].decode()) + if m: + return m.group(1) + except Exception: + # Ignore decoding error or index error + pass + with open(self.fullpath, 'rb') as f: line = f.read(80) if line and line[:3] == BOM_UTF8: return 'utf-8' if line and line[0] == 35: - n = line.find(b'\n') - m = research(r'coding[=:]\s*([-\w.]+)', line[:n].decode()) - if m: - return m.group(1) - if n > -1 and len(line) > (n+1) and line[n+1] == 35: - k = n + 1 - n = line.find(b'\n', k) - m = research(r'coding[=:]\s*([-\w.]+)', line[k:n].decode()) - if m: - return m.group(1) + encoding = find_encoding(line) or encoding return encoding def readlines(self, encoding=None): From f2f72a05266c91477bc76fb80783c778fbd62525 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 11 Nov 2025 18:06:29 +0800 Subject: [PATCH 269/337] Enable build target --vmc --- src/cli/command.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index 4a65a25f..531063b4 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -140,11 +140,6 @@ def build_parser(self, subparsers): # action="store_const", const='ecc', # help='genetate scripts with embedded C code' # ) - # group.add_argument( - # '--vmc', dest='target', default='std', - # action="store_const", const='vmc', - # help='genetate scripts with vm C code' - # ) group.add_argument( '--rft', dest='target', default='std', action="store_const", const='rft', @@ -160,6 +155,16 @@ def build_parser(self, subparsers): action="store_const", const='mini-rft', help='genetate high performance refactor scripts' ) + group.add_argument( + '--vmc', dest='target', default='std', + action="store_const", const='vmc', + help='genetate scripts with vmc code' + ) + group.add_argument( + '--vmc-rft', dest='target', default='std', + action="store_const", const='vmc-rft', + help='generate scripts by combining --rft and --vmc' + ) group.add_argument( '--list', dest='target', default='std', action="store_const", const='list', @@ -412,6 +417,8 @@ def cmd_build(self, ctx, args): logger.info('build target %s ...', args.target) output = args.output if args.output else 'dist' self._build(project, args.target, output) + if args.target.startswith('std'): + self._cmd_gen(args) logger.info('build target %s end', args.target) def _build(self, project, target, output, value=None): @@ -420,6 +427,25 @@ def _build(self, project, target, output, value=None): m = Pytransform3.init(self.ctx) m.pre_build(args) + def _cmd_gen(self, args): + """If build target is std, then call `pyarmor gen` + + It will obfuscate the scripts with `pyarmor_runtime.so` + + Most of obfuscated options are got from config file, not from + command line + + Option `--pack` may be still got from command line + """ + from .generate import Builder + from .plugin import Plugin + + ctx = self.ctx + builder = Builder(ctx) + Plugin.install(ctx) + builder.process(args) + Plugin.post_build(ctx) + def cmd_env(self, ctx, args): """Check and change pyarmor settings From 25a370c39472a2ad8a86f985c6f283c5973a46c6 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 22 Nov 2025 17:20:29 +0800 Subject: [PATCH 270/337] Add build targets: --ecc and --ecc-rft --- src/cli/command.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index 531063b4..48008468 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -135,11 +135,6 @@ def build_parser(self, subparsers): # action="store_const", const='std', # help='genetate standard obfuscated scripts' # ) - # group.add_argument( - # '--ecc', dest='target', default='std', - # action="store_const", const='ecc', - # help='genetate scripts with embedded C code' - # ) group.add_argument( '--rft', dest='target', default='std', action="store_const", const='rft', @@ -153,7 +148,7 @@ def build_parser(self, subparsers): group.add_argument( '--mini-rft', dest='target', default='std', action="store_const", const='mini-rft', - help='genetate high performance refactor scripts' + help='generate scripts by combining --rft and --mini' ) group.add_argument( '--vmc', dest='target', default='std', @@ -165,6 +160,16 @@ def build_parser(self, subparsers): action="store_const", const='vmc-rft', help='generate scripts by combining --rft and --vmc' ) + group.add_argument( + '--ecc', dest='target', default='std', + action="store_const", const='ecc', + help='genetate scripts with embedded C code' + ) + group.add_argument( + '--ecc-rft', dest='target', default='std', + action="store_const", const='ecc', + help='generate scripts by combining --rft and --ecc' + ) group.add_argument( '--list', dest='target', default='std', action="store_const", const='list', @@ -175,6 +180,10 @@ def build_parser(self, subparsers): action="store_const", const='types', help=argparse.SUPPRESS ) + + group = parser.add_argument_group( + 'Refactor options' + ) group.add_argument( '--randname', type=int, metavar='{0,1}', help='Build random name pool' From f88aa776c8f021d5c22ff169e2ddb74cd668127c Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 22 Nov 2025 22:04:16 +0800 Subject: [PATCH 271/337] Increase mini.core version --- src/cli/default.cfg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 93f063ba..6c77df21 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -5,12 +5,12 @@ major = 9 minor = 2 patch = 1 -;; Compatible core version +;; Compatible core version for package: pyarmor.cli.core cli.core = 8.1.0 -mini.core = 2.0 -;; ecc.core = 1.0 -;; vmc.core = 1.0 +;; Compatible core version for package: pyarmor.mini +;; Which is required to run MINI, VMC, or ECC mode scripts +mini.core = 3.0 ;; Deprecated since Pyarmor 8.2.5 ; cli.runtime = 3.2.5 From 75cdb42e12b1d736f2483d36ee3e46a8c3eae19d Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 22 Nov 2025 22:08:36 +0800 Subject: [PATCH 272/337] Fix bug --- src/cli/command.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index 48008468..1ee0bad1 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -180,10 +180,6 @@ def build_parser(self, subparsers): action="store_const", const='types', help=argparse.SUPPRESS ) - - group = parser.add_argument_group( - 'Refactor options' - ) group.add_argument( '--randname', type=int, metavar='{0,1}', help='Build random name pool' From d405752003125026a61cddeb5db45349475adbc1 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 25 Nov 2025 09:30:41 +0800 Subject: [PATCH 273/337] Change version to 9.2.2 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 629cb5ad..8cce89d1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.2' # The full version, including alpha/beta/rc tags. -release = '9.2.1' +release = '9.2.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index df8e0cf9..6e22c1ed 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.2.1' +__VERSION__ = '9.2.2' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 6c77df21..bd758d67 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 2 -patch = 1 +patch = 2 ;; Compatible core version for package: pyarmor.cli.core cli.core = 8.1.0 diff --git a/src/config.py b/src/config.py index f0f7ce25..d026e371 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.2.1' +version = '9.2.2' # The corresponding version of pytransform.so core_version = 'r52.6' From 6c2c180bc7d1dd155e75d9dfa4794b2ce08e6f4b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 26 Nov 2025 09:42:52 +0800 Subject: [PATCH 274/337] Add shebang property to project Module type --- src/cli/project.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/cli/project.py b/src/cli/project.py index b982d7f5..40ca2878 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -151,6 +151,8 @@ def __init__(self, path, name=None, parent=None): self._tree = None self._type = None + self._shebang = '' + @property def name(self): return '' if self._name == '__init__' else self._name @@ -190,6 +192,10 @@ def destpath(self): s = self.qualname + ('' if self.name else '.__init__') return joinpath(*s.split('.')) + splitext(self.path)[-1] + @property + def shebang(self): + return self._shebang + def compile_file(self, force=False): if self._co is not None and not force: return @@ -206,7 +212,9 @@ def parse_file(self, force=False): filename = self.abspath with open(filename, 'rb') as f: - encoding, _ = tokenize.detect_encoding(f.readline) + encoding, lines = tokenize.detect_encoding(f.readline) + if lines and lines[0].startswith('#!'): + self._shebang = lines[0] with open(filename, 'r', encoding=encoding) as f: logger.info('parse %s ...', self.qualname) From 9e394fefd4dc0ee175d140a41c21355b2de1202b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 26 Nov 2025 09:57:53 +0800 Subject: [PATCH 275/337] Fix shebang not str issue --- src/cli/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/project.py b/src/cli/project.py index 40ca2878..0b92b278 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -214,7 +214,7 @@ def parse_file(self, force=False): with open(filename, 'rb') as f: encoding, lines = tokenize.detect_encoding(f.readline) if lines and lines[0].startswith('#!'): - self._shebang = lines[0] + self._shebang = lines[0].encode(encoding) with open(filename, 'r', encoding=encoding) as f: logger.info('parse %s ...', self.qualname) From f5553a38850484defba604fd87db5d7d50267949 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 26 Nov 2025 19:06:48 +0800 Subject: [PATCH 276/337] Fix typo --- src/cli/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/project.py b/src/cli/project.py index 0b92b278..7a00f8b4 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -23,7 +23,7 @@ # # - Define project object for Pyarmor 9. # - Define project commands: init, build -# - Define targets: std, fly, vmc, ecc, rft +# - Define targets: std, mini, vmc, ecc, rft """Manage projects From 16cce011434d5b872ce47bf55c47eefce7f994f1 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 26 Nov 2025 21:09:05 +0800 Subject: [PATCH 277/337] Add parse_options for project --- src/cli/project.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/cli/project.py b/src/cli/project.py index 7a00f8b4..8226c95e 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -213,12 +213,13 @@ def parse_file(self, force=False): filename = self.abspath with open(filename, 'rb') as f: encoding, lines = tokenize.detect_encoding(f.readline) - if lines and lines[0].startswith('#!'): - self._shebang = lines[0].encode(encoding) + if lines and lines[0].startswith(b'#!'): + self._shebang = lines[0].decode(encoding) + options = self.project.parse_options with open(filename, 'r', encoding=encoding) as f: logger.info('parse %s ...', self.qualname) - self._tree = ast.parse(f.read(), filename, 'exec') + self._tree = ast.parse(f.read(), filename, 'exec', **options) logger.info('parse %s end', self.qualname) def _as_dot(self): @@ -393,6 +394,8 @@ def __init__(self, ctx): self._rft_include_attrs = None self._used_external_types = None + self._parse_options = None + # Log variable name in chain attributes # # For example, in module "foo.py": @@ -504,6 +507,17 @@ def namespaces(self): for x in self._namespaces: yield x + @property + def parse_options(self): + """Options for ast.parse when parsing project module""" + if self._parse_options is None: + cfg = self.ctx.cfg + optimize = cfg['builder'].getint('optimize', -1) + self._parse_options = { + 'optimize': optimize + } + return self._parse_options + @property def rft_options(self): """Refactor options: From 74f83518ad42e68643da1a9e5ec688b6623155e3 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 28 Nov 2025 12:55:23 +0800 Subject: [PATCH 278/337] Fix issue #2257 --- src/cli/default.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index bd758d67..c15bbda3 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -437,7 +437,7 @@ enable_comprehension = 1 [windows.x86_64.bcc] cc = clang.exe -cflags = -O3 -Wno-unsequenced -fno-asynchronous-unwind-tables -fno-unwind-tables --target=x86_64-elf-windows -c -DENABLE_BCC_MEMSET +cflags = -O3 -Wno-unsequenced -fno-asynchronous-unwind-tables -fno-unwind-tables --target=x86_64-elf-windows -c -DENABLE_BCC_MEMSET -DENABLE_PYAPI_REFCNT [windows.x86.bcc] cc = clang.exe From a48e6608a91592e8e557fbbad9291ee3774decf3 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 30 Nov 2025 08:20:31 +0800 Subject: [PATCH 279/337] Using utf-8 as default encoding for configuration file --- src/cli/config.py | 6 +++--- src/cli/context.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/cli/config.py b/src/cli/config.py index 6c56f7d4..3c8f2a9a 100644 --- a/src/cli/config.py +++ b/src/cli/config.py @@ -43,7 +43,7 @@ class Configer(object): def __init__(self, ctx, encoding=None): self.ctx = ctx - self._encoding = encoding + self._encoding = encoding or ctx.encoding def _read_config(self, filename): cfg = configparser.ConfigParser(empty_lines_in_values=False) @@ -166,7 +166,7 @@ def _set_option(self, sect, opt, value, local=True, name=None): cfg.set(sect, optname, optvalue) os.makedirs(os.path.dirname(filename), exist_ok=True) - with open(filename, 'w') as f: + with open(filename, 'w', encoding=self._encoding) as f: cfg.write(f) self._list_value(sect, optname, local=local, name=name) @@ -196,7 +196,7 @@ def _remove(self, section=None, options=None, local=True, name=None): logger.info('remove empty section "%s"', section) cfg.remove_section(section) - with open(filename, 'w') as f: + with open(filename, 'w', encoding=self._encoding) as f: cfg.write(f) def _clear(self, section=None, options=None, local=True, name=None): diff --git a/src/cli/context.py b/src/cli/context.py index 5214fb42..83b61596 100644 --- a/src/cli/context.py +++ b/src/cli/context.py @@ -164,9 +164,9 @@ def __init__(self, home, gpath='', lpath='', rpath='', encoding=None): os.path.join(self.home_path, rpath) # self.encoding is just for reading config file - self.encoding = encoding + self.encoding = encoding if encoding else 'utf-8' cfglist = self.default_config, self.global_config, self.local_config - self.cfg = self._read_config(cfglist, encoding=encoding) + self.cfg = self._read_config(cfglist, encoding=self.encoding) # self.runtime_package = 'pyarmor_runtime' # self.runtime_suffix = '_000000' @@ -205,6 +205,7 @@ def _read_config(self, filelist, encoding=None): empty_lines_in_values=False, interpolation=configparser.ExtendedInterpolation(), ) + encoding = encoding or self.encoding cfg.read(filelist, encoding=encoding) return cfg From ef4a651958fb18bf5540aa05d6ac6af4818db225 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 30 Nov 2025 08:36:48 +0800 Subject: [PATCH 280/337] Add keyword encoding for open text file --- src/cli/command.py | 2 +- src/cli/plugin.py | 10 +++++----- src/cli/shell.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index 1ee0bad1..2e77118b 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -348,7 +348,7 @@ def format_path(plist, raw=False): logger.info('%-20s: %s', key, value) makedirs(ctx.local_path, exist_ok=True) - with open(ctx.local_config, 'w') as f: + with open(ctx.local_config, 'w', encoding=ctx.encoding) as f: cfg.write(f) logger.info('project saved') diff --git a/src/cli/plugin.py b/src/cli/plugin.py index 628bb135..b6a696c3 100644 --- a/src/cli/plugin.py +++ b/src/cli/plugin.py @@ -186,7 +186,7 @@ def post_build(ctx, inputs, outputs, pack): os.makedirs(verpath) pkgscript = os.path.join(pkgpath, '__init__.py') - with open(pkgscript) as f: + with open(pkgscript, encoding=ctx.encoding) as f: lines = f.readlines() start = 1 if lines[0].startswith('#') else 0 @@ -196,13 +196,13 @@ def post_build(ctx, inputs, outputs, pack): '{0} = __import__("py%d%d.pyarmor_runtime" % py_version[:2],' ' globals(), locals(), ["{0}"], 1).{0}'.format('__pyarmor__') ]) - with open(pkgscript, 'w') as f: + with open(pkgscript, 'w', encoding=ctx.encoding) as f: f.write(''.join(lines)) for x in MultiPythonPlugin.RUNTIME_FILES: move(x, verpath) else: lines[start:start] = 'from sys import version_info as py_version\n' - with open(pkgscript, 'w') as f: + with open(pkgscript, 'w', encoding=ctx.encoding) as f: f.write(''.join(lines).replace( "join(['_'", "join(['py%d%d' % py_version[:2], '_'")) for x in MultiPythonPlugin.RUNTIME_FILES: @@ -253,7 +253,7 @@ def post_build(ctx, inputs, outputs, pack): return def rebuild_init(oneplat, init_script): - with open(init_script, 'r') as f: + with open(init_script, 'r', encoding=ctx.encoding) as f: lines = f.readlines() if oneplat: lines[1:] = ['from .pyarmor_runtime import __pyarmor__'] @@ -262,7 +262,7 @@ def rebuild_init(oneplat, init_script): if lines[i].strip().startswith("# mach = 'universal'"): lines[i] = lines[i].replace('# ', '') break - with open(init_script, 'w') as f: + with open(init_script, 'w', encoding=ctx.encoding) as f: f.write(''.join(lines)) rtpath = find_runtime_package(ctx, outputs[0]) diff --git a/src/cli/shell.py b/src/cli/shell.py index d21ad2e7..88ed45ab 100644 --- a/src/cli/shell.py +++ b/src/cli/shell.py @@ -86,7 +86,7 @@ def reset(self, index): self._reader = self._writer = None def save(self): - with open(self.cfgfile, 'w') as f: + with open(self.cfgfile, 'w', encoding=self._encoding) as f: self.writer.write(f) @@ -260,7 +260,7 @@ def __init__(self, ctx, domain='local'): ctx.default_config, ctx.global_config, ctx.local_config - ]) + ], encoding=ctx.encoding) self._domains = self._init_domains() self._domain = self._section = self._domains[domain] From c02a777b236465b79f7c6ff30babe5f61eaadbf4 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 30 Nov 2025 08:40:01 +0800 Subject: [PATCH 281/337] Project ruler files always use utf-8 encoding --- src/cli/project.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cli/project.py b/src/cli/project.py index 8226c95e..644251a7 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -802,7 +802,7 @@ def used_external_types(self): for name in names.split(): if name.endswith('.json'): if exists(name): - with open(name) as f: + with open(name, encoding='utf-8') as f: used_types.update(jsonload(f)) else: modname = name.split('::')[0] @@ -1035,7 +1035,7 @@ def preview_autofix_result(self, mode): if not exists(rulefile): logger.info('no found %s', rulefile) return - with open(rulefile) as f: + with open(rulefile, encoding='utf-8') as f: fixtable = jsonload(f) if mode == 2: @@ -1093,7 +1093,7 @@ def _preview_autofix_2(self, fixtable, output): inattrs = fixtable.get('include_attrs', []) rftattrs = header.substitute(attrs='\n'.join( [f'[[*{x}][{x}]]' for x in inattrs])) - with open(output, 'w') as fp: + with open(output, 'w', encoding='utf-8') as fp: fp.write(rftattrs) for attr in inattrs: infos = attrinfo.get(attr) @@ -1186,7 +1186,7 @@ def _preview_autofix_3(self, fixtable, output): """)) attrs = sorted(rftattrs.keys()) - with open(output, 'w') as fp: + with open(output, 'w', encoding='utf-8') as fp: fp.write(header.substitute( attrs='\n'.join([f'[[*{x}][{x}]]' for x in attrs]) )) From 0c1c5ee9d210726bd6465ce7536b283afacded4e Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 1 Dec 2025 20:31:06 +0800 Subject: [PATCH 282/337] Change parse_options to compile_options because prior to Python 3.12, there is no keyword `optimize` in ast.parse --- src/cli/project.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cli/project.py b/src/cli/project.py index 644251a7..7b9f6538 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -202,8 +202,9 @@ def compile_file(self, force=False): self.parse_file(force=force) + options = self.project.compile_options logger.info('compile %s ...', self.qualname) - self._co = compile(self._tree, self.abspath, 'exec') + self._co = compile(self._tree, self.abspath, 'exec', **options) logger.info('compile %s end', self.qualname) def parse_file(self, force=False): @@ -216,10 +217,9 @@ def parse_file(self, force=False): if lines and lines[0].startswith(b'#!'): self._shebang = lines[0].decode(encoding) - options = self.project.parse_options with open(filename, 'r', encoding=encoding) as f: logger.info('parse %s ...', self.qualname) - self._tree = ast.parse(f.read(), filename, 'exec', **options) + self._tree = ast.parse(f.read(), filename, 'exec') logger.info('parse %s end', self.qualname) def _as_dot(self): @@ -394,7 +394,7 @@ def __init__(self, ctx): self._rft_include_attrs = None self._used_external_types = None - self._parse_options = None + self._compile_options = None # Log variable name in chain attributes # @@ -508,15 +508,15 @@ def namespaces(self): yield x @property - def parse_options(self): + def compile_options(self): """Options for ast.parse when parsing project module""" - if self._parse_options is None: + if self._compile_options is None: cfg = self.ctx.cfg optimize = cfg['builder'].getint('optimize', -1) - self._parse_options = { + self._compile_options = { 'optimize': optimize } - return self._parse_options + return self._compile_options @property def rft_options(self): From 2165df4a439e0141860984de468dbe23bad77227 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 2 Dec 2025 07:54:36 +0800 Subject: [PATCH 283/337] Fix pyarmor_mini location issue --- tests.8/Makefile | 2 +- tests.8/accept_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests.8/Makefile b/tests.8/Makefile index f8f809fb..c97632c9 100644 --- a/tests.8/Makefile +++ b/tests.8/Makefile @@ -105,9 +105,9 @@ test-%: install-%: @rm -rf ./pyarmor ./pyarmor_cli_core_themida* @tar xzf ${pkgpath}/pyarmor-${testver}.tar.gz - -@test -d ${pkgpath}/mini.extensions/${HOST} && cp ${pkgpath}/mini.extensions/${HOST}/libs/cp${PYVER}/pyarmor_mini.* . @mv pyarmor-${testver}/pyarmor . @rm -rf pyarmor-${testver} + -@test -d ${pkgpath}/mini.extensions/${HOST} && mkdir -p pyarmor/mini/ && cp ${pkgpath}/mini.extensions/${HOST}/libs/cp${PYVER}/pyarmor_mini.* ./pyarmor/mini/ @unzip ${whlpath}/pyarmor_cli_core-${corever}-cp$(subst .,,$(subst install-,,$@))-none-${platform}.whl > /dev/null @(themida=../pyarmor_cli_core_themida-${corever}-cp$(subst .,,$(subst install-,,$@))-none-any.whl && test -f $$themida && unzip $$themida > /dev/null || echo no themida $$themida) @rm -rf pyarmor_cli_core-${corever}.dist-info diff --git a/tests.8/accept_test.py b/tests.8/accept_test.py index efd9cd6f..58d211ad 100755 --- a/tests.8/accept_test.py +++ b/tests.8/accept_test.py @@ -60,7 +60,7 @@ def is_trial(self): def copy_mini_extension(self, output='dist'): ext = '.pyd' if sys.platform == 'windows' else '.so' - shutil.copy(f'pyarmor_mini{ext}', output) + shutil.copy(f'pyarmor/mini/pyarmor_mini{ext}', output) def tearDown(self): shutil.rmtree(self.local_path, ignore_errors=True) From bf60feae0ee584fec89d3ca8d582bd22e3707eca Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 2 Dec 2025 10:09:54 +0800 Subject: [PATCH 284/337] Add config option `mini_extension` --- src/cli/default.cfg | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index c15bbda3..4f4b5477 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -235,6 +235,15 @@ rft_mix_import_name = 0 ;; Strip leading name package strip_package_name = 1 +;; Since Pyarmor 9.2.2, MINI script bootstrap code is changed: +;; +;; - from pyarmor_mini import __pyarmor__ +;; + from pyarmor.mini.pyarmor_mini import __pyarmor__ +;; +;; If need old code, uncomment this option +;; +;; mini_extension = pyarmor_mini + ;; ;; Compatiable options ;; From 4c67759d78c7898da01cfddd1592845b262dc973 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 3 Dec 2025 10:07:30 +0800 Subject: [PATCH 285/337] Check Python version when run `pyarmor build` If only works for Python 3.9+ --- src/cli/command.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cli/command.py b/src/cli/command.py index 2e77118b..5b14690b 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -376,6 +376,10 @@ def cmd_build(self, ctx, args): ) cfg.read([ctx.local_config], encoding=ctx.encoding) + x, y = ctx.python_version + if not (x == 3 and y > 8 and y < 16): + raise CliError('`pyarmor build` only works for Python 3.9+') + sectname = 'project' if not cfg.has_section(sectname): raise CliError('no project information') From 15dfa5c3c377cb1f156969fe0b9674fd45b5fd4c Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 6 Dec 2025 08:58:39 +0800 Subject: [PATCH 286/337] Add property `freethreadin` in building context --- src/cli/context.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cli/context.py b/src/cli/context.py index 83b61596..ba9ece52 100644 --- a/src/cli/context.py +++ b/src/cli/context.py @@ -331,6 +331,11 @@ def version(self): def python_version(self): return sys.version_info[:2] + @property + def freethreading(self): + from sysconfig import get_config_var + return 1 if get_config_var("Py_GIL_DISABLED") else 0 + @property def default_config(self): return os.path.join(os.path.dirname(__file__), 'default.cfg') From 0ab6a53aa1347ceb7c8869460e43203e5ee288b8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 6 Dec 2025 09:08:01 +0800 Subject: [PATCH 287/337] Refine name --- src/cli/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/context.py b/src/cli/context.py index ba9ece52..4e21468a 100644 --- a/src/cli/context.py +++ b/src/cli/context.py @@ -332,7 +332,7 @@ def python_version(self): return sys.version_info[:2] @property - def freethreading(self): + def free_threading(self): from sysconfig import get_config_var return 1 if get_config_var("Py_GIL_DISABLED") else 0 From 0920b977e8669ddb21bc71722f3e16f560abb1c7 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 6 Dec 2025 10:15:56 +0800 Subject: [PATCH 288/337] Rename freereading to py_gil_disabled --- src/cli/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/context.py b/src/cli/context.py index 4e21468a..a3c73969 100644 --- a/src/cli/context.py +++ b/src/cli/context.py @@ -332,7 +332,7 @@ def python_version(self): return sys.version_info[:2] @property - def free_threading(self): + def py_gil_disabled(self): from sysconfig import get_config_var return 1 if get_config_var("Py_GIL_DISABLED") else 0 From 31cca95d509ad02e527717c8257659b5a7f65b34 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 6 Dec 2025 15:48:54 +0800 Subject: [PATCH 289/337] Remove cflags --- src/cli/default.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 4f4b5477..7f3c43ca 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -446,7 +446,7 @@ enable_comprehension = 1 [windows.x86_64.bcc] cc = clang.exe -cflags = -O3 -Wno-unsequenced -fno-asynchronous-unwind-tables -fno-unwind-tables --target=x86_64-elf-windows -c -DENABLE_BCC_MEMSET -DENABLE_PYAPI_REFCNT +cflags = -O3 -Wno-unsequenced -fno-asynchronous-unwind-tables -fno-unwind-tables --target=x86_64-elf-windows -c -DENABLE_BCC_MEMSET [windows.x86.bcc] cc = clang.exe From 94190df3f08e0ff4c284380258b97e2fbf2e1525 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 7 Dec 2025 07:10:20 +0800 Subject: [PATCH 290/337] Remove debug option --- src/cli/default.cfg | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 7f3c43ca..bd758d67 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -235,15 +235,6 @@ rft_mix_import_name = 0 ;; Strip leading name package strip_package_name = 1 -;; Since Pyarmor 9.2.2, MINI script bootstrap code is changed: -;; -;; - from pyarmor_mini import __pyarmor__ -;; + from pyarmor.mini.pyarmor_mini import __pyarmor__ -;; -;; If need old code, uncomment this option -;; -;; mini_extension = pyarmor_mini - ;; ;; Compatiable options ;; From e5e732867b50039b203e916748e39d650cc059d3 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 7 Dec 2025 08:19:43 +0800 Subject: [PATCH 291/337] Add project options for std/ecc/vmc mode --- src/cli/model.py | 9 +++++ src/cli/project.py | 91 +++++++++++++++++++++++++++++++++++----------- src/cli/shell.py | 3 ++ 3 files changed, 82 insertions(+), 21 deletions(-) diff --git a/src/cli/model.py b/src/cli/model.py index 7c96db4b..697d8093 100644 --- a/src/cli/model.py +++ b/src/cli/model.py @@ -401,6 +401,15 @@ class RftAttrFilter(ListOption): cls='DictOption', ), ], + 'mini': [ + OptionModel( + name='import_from', + cls='NameOption', + value='pyarmor.mini.pyarmor_mini', + ), + ], + 'vmc': [], + 'ecc': [], } diff --git a/src/cli/project.py b/src/cli/project.py index 7b9f6538..96159453 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -196,13 +196,15 @@ def destpath(self): def shebang(self): return self._shebang - def compile_file(self, force=False): + def compile_file(self, force=False, optimize=-1): if self._co is not None and not force: return self.parse_file(force=force) - options = self.project.compile_options + options = { + 'optimize': optimize + } logger.info('compile %s ...', self.qualname) self._co = compile(self._tree, self.abspath, 'exec', **options) logger.info('compile %s end', self.qualname) @@ -394,7 +396,10 @@ def __init__(self, ctx): self._rft_include_attrs = None self._used_external_types = None - self._compile_options = None + self._std_options = None + self._mini_options = None + self._vmc_options = None + self._ecc_options = None # Log variable name in chain attributes # @@ -508,15 +513,18 @@ def namespaces(self): yield x @property - def compile_options(self): - """Options for ast.parse when parsing project module""" - if self._compile_options is None: + def std_options(self): + """Options got from ctx.builder, not in project""" + if self._std_options is None: cfg = self.ctx.cfg optimize = cfg['builder'].getint('optimize', -1) - self._compile_options = { + self._std_options = { 'optimize': optimize } - return self._compile_options + return self._std_options + + def std_opt(self, name): + return self.std_options.get(name) @property def rft_options(self): @@ -786,6 +794,60 @@ def rft_type_rules(self): self._rft_type_rules = vartypes return self._rft_type_rules + @property + def mini_options(self): + """MINI mode options: + + - import_from: str + + Default value is "pyarmor.mini.pyarmor_mini", it means from + package `pyarmor.mini` import extension `pyarmor_mini` + + Set it to "pyarmor_mini" if it's distributed with obfuscated + scripts + """ + if self._mini_options is None: + cfg = self.ctx.cfg + sect = 'mini' + if cfg.has_section(sect): + self._mini_options = dict(cfg.items(sect)) + else: + self._mini_options = {} + return self._mini_options + + def mini_opt(self, name): + return self.mini_options.get(name) + + @property + def vmc_options(self): + """VMC mode options""" + if self._vmc_options is None: + cfg = self.ctx.cfg + sect = 'vmc' + if cfg.has_section(sect): + self._vmc_options = dict(cfg.items(sect)) + else: + self._vmc_options = {} + return self._vmc_options + + def vmc_opt(self, name): + return self.vmc_options.get(name) + + @property + def ecc_options(self): + """ECC mode options""" + if self._ecc_options is None: + cfg = self.ctx.cfg + sect = 'ecc' + if cfg.has_section(sect): + self._ecc_options = dict(cfg.items(sect)) + else: + self._ecc_options = {} + return self._ecc_options + + def ecc_opt(self, name): + return self.ecc_options.get(name) + @property def builtins(self): if self._builtins is None: @@ -841,19 +903,6 @@ def rft_include_attrs(self): self._rft_include_attrs.update(value.split()) return self._rft_include_attrs - def std_options(self): - """Obfuscation options only for std target - - - std_assert_import - - std_assert_call - - std_restrict_module - - std_expired_date - - std_bind_devices - - Got from command line, not in config file - """ - pass - def get_module(self, qualname): """Get module in the project by unique qualname It equals one dict: map_qualname_to_module diff --git a/src/cli/shell.py b/src/cli/shell.py index 88ed45ab..8325b0cc 100644 --- a/src/cli/shell.py +++ b/src/cli/shell.py @@ -281,6 +281,9 @@ def _init_domains(self): domain.add_group(name, GROUPS[name]) ps.add_section('rft') + ps.add_section('mini') + ps.add_section('vmc') + ps.add_section('ecc') return {'global': gs, 'local': ls, 'project': ps} From be3b9dadc34528bfb399910651fb9e65b8defccd Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 7 Dec 2025 11:27:13 +0800 Subject: [PATCH 292/337] Fix option mini:import_from default value issue --- src/cli/project.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cli/project.py b/src/cli/project.py index 96159453..fd4b0a62 100755 --- a/src/cli/project.py +++ b/src/cli/project.py @@ -813,6 +813,8 @@ def mini_options(self): self._mini_options = dict(cfg.items(sect)) else: self._mini_options = {} + self._mini_options.setdefault( + 'import_from', 'pyarmor.mini.pyarmor_mini') return self._mini_options def mini_opt(self, name): From e019ee9d4bdfc8064a08d25237b62ca0e53a742e Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 7 Dec 2025 11:28:01 +0800 Subject: [PATCH 293/337] Init v9 test scripts --- tests.8/Makefile | 5 +- tests.8/accept_test.py | 40 -- tests.9/Makefile | 118 ++++++ tests.9/README.md | 7 + tests.9/accept_test.py | 87 ++++ tests.9/genscript.py | 916 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1132 insertions(+), 41 deletions(-) create mode 100644 tests.9/Makefile create mode 100644 tests.9/README.md create mode 100755 tests.9/accept_test.py create mode 100755 tests.9/genscript.py diff --git a/tests.8/Makefile b/tests.8/Makefile index c97632c9..a5780730 100644 --- a/tests.8/Makefile +++ b/tests.8/Makefile @@ -107,7 +107,6 @@ install-%: @tar xzf ${pkgpath}/pyarmor-${testver}.tar.gz @mv pyarmor-${testver}/pyarmor . @rm -rf pyarmor-${testver} - -@test -d ${pkgpath}/mini.extensions/${HOST} && mkdir -p pyarmor/mini/ && cp ${pkgpath}/mini.extensions/${HOST}/libs/cp${PYVER}/pyarmor_mini.* ./pyarmor/mini/ @unzip ${whlpath}/pyarmor_cli_core-${corever}-cp$(subst .,,$(subst install-,,$@))-none-${platform}.whl > /dev/null @(themida=../pyarmor_cli_core_themida-${corever}-cp$(subst .,,$(subst install-,,$@))-none-any.whl && test -f $$themida && unzip $$themida > /dev/null || echo no themida $$themida) @rm -rf pyarmor_cli_core-${corever}.dist-info @@ -120,3 +119,7 @@ ptest: clean: rm -rf ${homepath} ${localpath} ./pyarmor ./pyarmor-* ./pyarmor.*.log + +# Unused +install-mini: + -@test -d ${pkgpath}/mini.extensions/${HOST} && mkdir -p pyarmor/mini/ && cp ${pkgpath}/mini.extensions/${HOST}/libs/cp${PYVER}/pyarmor_mini.* ./pyarmor/mini/ diff --git a/tests.8/accept_test.py b/tests.8/accept_test.py index 58d211ad..344c4ef5 100755 --- a/tests.8/accept_test.py +++ b/tests.8/accept_test.py @@ -38,16 +38,6 @@ def wrap(self, *args, **kwargs): return wrap -def only_v9test(func): - - def wrap(self, *args, **kwargs): - if self.is_trial() or sys.version_info[1] < 9: - self.skipTest('v9 pro case') - func(self, *args, **kwargs) - - return wrap - - class BaseTestCase(unittest.TestCase): def setUp(self): @@ -58,10 +48,6 @@ def setUp(self): def is_trial(self): return not os.path.exists(os.path.join(self.home, 'license.lic')) - def copy_mini_extension(self, output='dist'): - ext = '.pyd' if sys.platform == 'windows' else '.so' - shutil.copy(f'pyarmor/mini/pyarmor_mini{ext}', output) - def tearDown(self): shutil.rmtree(self.local_path, ignore_errors=True) shutil.rmtree(self.default_output, ignore_errors=True) @@ -399,32 +385,6 @@ def test_thread_with_bcc(self): 'hello bob', ]])) - @only_v9test - def test_v9_rft_foo(self): - args = ['init', '-e', 'samples/foo.py'] - self.pyarmor_cmd(args) - args = ['build', '--rft'] - self.pyarmor_cmd(args) - self.verify_dist_foo() - - @only_v9test - def test_v9_mini_foo(self): - args = ['init', '-e', 'samples/foo.py'] - self.pyarmor_cmd(args) - args = ['build', '--mini'] - self.pyarmor_cmd(args) - self.copy_mini_extension() - self.verify_dist_foo() - - @only_v9test - def test_v9_mini_rft_foo(self): - args = ['init', '-e', 'samples/foo.py'] - self.pyarmor_cmd(args) - args = ['build', '--mini-rft'] - self.pyarmor_cmd(args) - self.copy_mini_extension() - self.verify_dist_foo() - if __name__ == '__main__': logging.getLogger().addHandler(logging.NullHandler()) diff --git a/tests.9/Makefile b/tests.9/Makefile new file mode 100644 index 00000000..249b2c96 --- /dev/null +++ b/tests.9/Makefile @@ -0,0 +1,118 @@ +# +# Run tests in different platforms and different Python +# +# 1. Basic usage, test all pythons in this platform +# +# make test +# make test-nogil +# +# 2. Test one python version +# +# make test-3.13 +# +# 3. Test free-threaded version +# +# make test-3.14 NOGIL=y +# +HOSTS := darwin.x86_64 darwin.arm64 linux.x86_64 linux.aarch64 windows.x86_64 + +UNAME ?= $(shell uname) +ARCH ?= $(shell arch) +PYVERS ?= 3.9 3.10 3.11 3.12 3.13 3.14 3.15 + +PYVER ?= $(word 1,${PYVERS}) +ifneq ($(findstring ${PYVER},${PYVERS}),${PYVER}) + $(error "${PYVER}" not in supported Python versions "${PYVERS}") +endif + +ifeq ($(findstring Linux,$(UNAME)),Linux) + ifeq ($(ARCH),x86_64) + HOST ?= linux.x86_64 + else ifeq ($(ARCH),aarch64) + HOST ?= linux.aarch64 + else + HOST ?= linux.x86 + endif +else ifeq ($(findstring Darwin,$(UNAME)),Darwin) + ifeq ($(ARCH),arm64) + HOST ?= darwin.arm64 + else + HOST ?= darwin.x86_64 + endif +else + ifeq ($(ARCH),x86_64) + HOST ?=windows.x86_64 + else + HOST ?=windows.x86 + endif +endif + +ifeq ($(findstring windows,${HOST}),windows) + PYTHON := venv/bin/python$(subst .,,${PYVER}) +else + PYTHON := venv/bin/python${PYVER} +endif + +$(info python is ${PYTHON}) + +PLATFORM = $(strip $(subst linux.x86,manylinux1_i686,\ + $(subst linux.x86_64,manylinux1_x86_64,\ + $(subst linux.aarch64,manylinux2014_aarch64,\ + $(subst linux.armv7,manylinux2014_armv7l,\ + $(subst windows.x86,win32,\ + $(subst windows.x86_64,win_amd64,\ + $(subst darwin.arm64,macosx_11_0_arm64,\ + $(subst darwin.x86_64,macosx_10_14_x86_64,\ + ${HOST}))))))))) + +PYTAG=cp$(subst .,,${PYVER}) + +workpath := __runner__ +pkgpath := ${workpath}/pkgdist +testpkg := $(wildcard ${pkgpath}/pyarmor_cli-*-none-any.whl) +corepkg := $(wildcard ${pkgpath}/pyarmor_cli_core-*-${PYTAG}-*-${PLATFORM}.whl) +minipkg := $(wildcard ${pkgpath}/pyarmor_mini-*-${PYTAG}-${PYTAG}-${PLATFORM}.whl) + +ifndef testpkg +$(error no test package found) +endif + +ifndef corepkg +$(error no core package found) +endif + +ifndef minipkg +$(error no mini package found) +endif + +$(info got test package: $(basename ${testpkg})) +$(info got core package: $(basename ${corepkg})) +$(info got mini package: $(basename ${minipkg})) + +PIPOPTIONS := --force-reinstall --disable-pip-version-check + +.PHONY: all test test-nogil test-* install clean + +all: test test-nogil + +test: + for ver in ${PYVERS} ; do \ + make test-$$ver ; \ + done + +test-nogil: + for ver in ${PYVERS} ; do \ + make test-$$ver NOGIL=y ; \ + done + +install: + ${PYTHON} -m pip install ${PIPOPTIONS} ${corepkg} ${testpkg} ${minipkg} + +test-%: + PYVER=$(subst test-,,$@) make install run-test + +run-test: + ${PYTHON} ${workpath}/accept_test.py + +clean: + rm -rf ${workpath} __pycache__ testv9.tar.gz diff --git a/tests.9/README.md b/tests.9/README.md new file mode 100644 index 00000000..ba654e10 --- /dev/null +++ b/tests.9/README.md @@ -0,0 +1,7 @@ +# V9 Tests + +Test project, mini, rft, vmc, ecc scripts which are new features in Pyarmor 9+ + +Make sure package `pyarmor.cli>=9.2.2` and `pyarmor.mini>=3.0` has been installed: + + python accept_test.py diff --git a/tests.9/accept_test.py b/tests.9/accept_test.py new file mode 100755 index 00000000..adcc8908 --- /dev/null +++ b/tests.9/accept_test.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- + +import logging +import os +import shutil +import unittest + +from test.support import script_helper + +import genscript as script_generator + + +class BaseTestCase(unittest.TestCase): + + work_path = os.path.abspath(os.path.dirname(__file__)) + + @classmethod + def setUpClass(cls): + cls.local_path = os.path.join(cls.work_path, '.pyarmor') + cls.default_output = os.path.join(cls.work_path, 'dist') + + def setUp(self): + shutil.rmtree(self.local_path, ignore_errors=True) + + def tearDown(self): + shutil.rmtree(self.local_path, ignore_errors=True) + + def assert_python_ok(self, *args): + kwargs = { + '__isolated': False + } + return script_helper.assert_python_ok(*args, **kwargs) + + def assert_python_failure(self, *args): + kwargs = { + '__isolated': False + } + return script_helper.assert_python_failure(*args, **kwargs) + + def verify_script_pass(self, script, expected): + rc, stdout, stderr = self.assert_python_ok(script) + self.assertEqual(expected, (rc, stdout, stderr)) + + def build_v9_script(self, script, target): + kwargs = { + '__isolated': False, + '__cwd': self.work_path, + } + args = ['-m', 'pyarmor.cli', 'init', '-e', os.path.abspath(script)] + script_helper.assert_python_ok(*args, **kwargs) + + args = ['-m', 'pyarmor.cli', 'build', '--' + target] + script_helper.assert_python_ok(*args, **kwargs) + return os.path.join(self.default_output, os.path.basename(script)) + + def make_script(self, source, name='foo'): + return script_helper.make_script(self.work_path, name, source) + + def iter_scripts(self, catalog=''): + path = os.path.join('samples', catalog) + if os.path.exists(path): + with os.scandir(path) as it: + for entry in it: + if entry.name.endswith('.py') and entry.is_file(): + yield entry.path + + for name, source in script_generator.iter_scripts(catalog): + yield self.make_script(source, name=name) + + +class UnitTestCases(BaseTestCase): + + def test_vmc_mode(self): + for script in self.iter_scripts('modules'): + with self.subTest(script=script): + expected = self.assert_python_ok(script) + vmcscript = self.build_v9_script(script, 'vmc') + self.verify_script_pass(vmcscript, expected) + + +if __name__ == '__main__': + logging.getLogger().addHandler(logging.NullHandler()) + + loader = unittest.TestLoader() + # loader.testMethodPrefix = 'test_exclude_' + suite = loader.loadTestsFromTestCase(UnitTestCases) + result = unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tests.9/genscript.py b/tests.9/genscript.py new file mode 100755 index 00000000..4a4bc364 --- /dev/null +++ b/tests.9/genscript.py @@ -0,0 +1,916 @@ +# -*- coding: utf-8 -*- +# +# 生成脚本实例 +# +# 使用方法: +# +# from test.support import script_helper +# from driver import generator +# +# for name, source in generator.iter_scripts(''): +# script_helper.make_script(source, name=name) +# + +import itertools + +from string import Template +from textwrap import dedent, indent + + +INDENT = 4 +"""int: Python 脚本的缩进宽度""" + + +module_tpl = Template('''\ +$body +''') + +function_tpl = Template('''\ +def foo(): + $body +foo() +''') + +class_tpl = Template('''\ +class Foo(): + $body +Foo() +''') + +method_tpl = Template('''\ +class Foo(): + def run(self): + $body +Foo().run() +''') + +nest_function_tpl = Template('''\ +def foo(): + def nest(): + $body + nest() +foo() +''') + +nest_class_tpl = Template('''\ +$body +''') + +nest_class_method_tpl = Template('''\ +$body +''') + + +tpl_catalog = { + 'modules': (module_tpl, 0), + 'functions': (function_tpl, 1), + 'classes': (class_tpl, 1), + 'methods': (method_tpl, 2), + 'nest_functions': (nest_function_tpl, 2), +} + + +expr_call_materials = [ + ('call_1', dedent('''\ + print('ok') + ''')), + ('call_2', dedent('''\ + print('ok', end='$') + ''')), + ('call_3', dedent('''\ + print(*('ok', 'yes')) + ''')), + ('call_3_1', dedent('''\ + print(*['ok2', 'yes2']) + ''')), + ('call_4', dedent('''\ + print(*('ok', 'yes'), end='$') + ''')), + ('call_5', dedent('''\ + args = ('ok', 'yes') + kwargs = {'end': '$'} + print(*args, **kwargs) + ''')), + ('call_6', dedent('''\ + args = ('ok', 'yes') + kwargs = {'end': '$'} + print('abc', *args, '123', sep='\t', **kwargs) + ''')), +] + +assign_materials = [ + ('assign_1', dedent('''\ + a = 1 + print(a) + ''')), + ('assign_2', dedent('''\ + x = [0, 1] + x[1] = 2 + print(x) + ''')), + ('assign_3', dedent('''\ + x = [0, 1] + x[1:1] = 4, 5, 6 + print(x) + ''')), + ('assign_3_1', dedent('''\ + x = [0, 1] + x[:] = 4, 5, 6 + print(x) + ''')), + ('assign_3_2', dedent('''\ + x = [0, 1] + x[:1] = 4, 5, 6 + print(x) + ''')), + ('assign_3_3', dedent('''\ + x = [0, 1] + x[1:] = 4, 5, 6 + print(x) + ''')), + ('assign_4', dedent('''\ + x = {} + x['k'] = 2 + print(x) + ''')), + ('assign_5', dedent('''\ + class C: pass + a = C() + a.x = 1 + print(a.x) + ''')), + ('assign_6', dedent('''\ + a, b, c = 1, 2, 3 + print(a, b, c) + ''')), + ('assign_7', dedent('''\ + a, (b, c), d = 1, (2, 3), 5 + print(a, b, c, d) + ''')), + ('assign_7_1', dedent('''\ + a, [b, c], d = 1, (2, 3), 5 + print(a, b, c, d) + ''')), + ('assign_7_2', dedent('''\ + [b, c], = (2, 3), + print(b, c) + ''')), + ('assign_8', dedent('''\ + a, b, *c = 1, (2, 3), 5 + print(a, b, c) + ''')), + ('assign_8_1', dedent('''\ + a, *b, c, d = 1, 2, 3, 5, 6, 7 + print(a, b, c, d) + ''')), + ('assign_8_2', dedent('''\ + *a, (b, c), d = 1, (2, 3), 5 + print(a, b, c, d) + ''')), + ('assign_8_3', dedent('''\ + *a, = 1, (2, 3), 5, 6, 7 + print(a) + ''')), + ('assign_9', dedent('''\ + a, (b, c), *d = 1, (2, 3), 5, 6, 7 + print(a, b, c, d) + ''')), + ('assign_10', dedent('''\ + x = [0, 1] + i = 0 + i, x[i] = 1, 2 + print(i, x) + ''')) +] + +augassign_materials = [ + ('augassign_1', dedent('''\ + a = 1 + a += 2 + a -= 1 + a *= 3 + print(a) + ''')), + ('augassign_2', dedent('''\ + x = [0, 1] + x[1] += 2 + x[1] /= 3 + print(x) + ''')), + ('augassign_3', dedent('''\ + x = {} + x['k'] = 2 + x['k'] += 2 + print(x) + ''')), + ('augassign_4', dedent('''\ + class C: pass + a = C() + a.x = 1 + a.x += 1 + print(a.x) + ''')), +] + +annassign_materials = [ + ('annassign_1', dedent('''\ + a: int = 1 + print(a) + ''')), + ('annassign_2', dedent('''\ + (a): int = 1 + print(a) + ''')), + ('annassign_3', dedent('''\ + c:int + ''')), + ('annassign_4', dedent('''\ + x = [0, 1] + x[1]:int + print(x) + ''')), + ('annassign_5', dedent('''\ + class C: pass + a = C() + a.x:int = 2 + print(a.x) + ''')), +] + +delete_materials = [ + ('delete_1', dedent('''\ + a = 1 + del a + print('a: ', 'a' in locals()) + ''')), + ('delete_2', dedent('''\ + x = [0, 1, 2] + del x[1] + print(x) + ''')), + ('delete_2_1', dedent('''\ + x = [0, 1, 2] + del x[:] + print(x) + ''')), + ('delete_3', dedent('''\ + class C: pass + a = C() + a.x = 1 + print(a.x) + ''')), +] + +for_materials = [ + ('for_1', dedent('''\ + for i in (1, 2, 3): + print(i) + ''')), + ('for_2', dedent('''\ + for i, j in zip((1, 2, 3), (4, 5, 6)): + print(i + j) + ''')), + ('for_3', dedent('''\ + for i in (1, 2, 3): + print(i) + if i > 2: + break + ''')), + ('for_4', dedent('''\ + for i in (1, 2, 3): + if i > 2: + continue + print(i) + ''')), + ('for_5', dedent('''\ + for i in (1, 2, 3): + print(i) + else: + print('else ok') + ''')), + ('for_6', dedent('''\ + for i in (1, 2, 3): + print(i) + break + else: + print('else ok') + ''')), + ('for_7', dedent('''\ + for i in (1, 2, 3): + print(i) + break + else: + print('else ok') + ''')), +] + +while_materials = [ + ('while_1', dedent('''\ + i = 0 + while i < 3: + print(i) + i += 1 + ''')), + ('while_2', dedent('''\ + i = 0 + while i < 3: + print(i) + i += 1 + else: + print('else', i) + ''')), + ('while_3', dedent('''\ + i = 0 + while i < 5: + print(i) + if i > 3: + break + i += 1 + ''')), + ('while_4', dedent('''\ + i = 0 + while i < 5: + i += 1 + if i > 3: + continue + print(i) + ''')), + ('while_5', dedent('''\ + i = 0 + while i < 5: + i += 1 + if i > 3: + continue + print(i) + else: + print('else ok') + ''')), + ('while_6', dedent('''\ + i = 0 + while i < 5: + i += 1 + if i > 3: + break + print(i) + else: + print('else ok', i) + ''')), +] + +if_materials = [ + ('if_1', dedent('''\ + if 1: + print('if ok') + ''')), + ('if_2', dedent('''\ + if 0: + print('if ok') + else: + print('else ok') + ''')), +] + +listcomp_materials = [ + ('listcomp_1', dedent('''\ + a = [x for x in range(10)] + print(a) + ''')), + ('listcomp_2', dedent('''\ + a = [x for x in range(10) if x > 2] + print(a) + ''')), + ('listcomp_2_1', dedent('''\ + a = [x for x in range(10) if x < 6] + print(a) + ''')), + ('listcomp_3', dedent('''\ + a = [x for x in range(10) if x > 2 if x % 3 == 0] + print(a) + ''')), + ('listcomp_4', dedent('''\ + a = [(x, y) for x in range(3) for y in range(3)] + print(a) + ''')), + ('listcomp_5', dedent('''\ + a = [(x, y) for x in range(10) if x > 2 for y in range(10)] + print(a) + ''')), + ('listcomp_6', dedent('''\ + a = [(x, y) for x in range(10) for y in range(10) if y > x] + print(a) + ''')), + ('listcomp_7', dedent('''\ + a = [(x, y) for x in range(10) if x > 2 for y in range(10) if y > x] + print(a) + ''')), +] + +setcomp_materials = [ + ('setcomp_1', dedent('''\ + a = {x for x in range(10)} + print(a) + ''')), + ('setcomp_2', dedent('''\ + a = {x for x in range(10) if x > 2} + print(a) + ''')), + ('setcomp_2_1', dedent('''\ + a = {x for x in range(10) if x < 6} + print(a) + ''')), + ('setcomp_3', dedent('''\ + a = {x for x in range(10) if x > 2 if x % 3 == 0} + print(a) + ''')), + ('setcomp_4', dedent('''\ + a = {(x, y) for x in range(3) for y in range(3)} + print(a) + ''')), + ('setcomp_5', dedent('''\ + a = {(x, y) for x in range(10) if x > 2 for y in range(10)} + print(a) + ''')), + ('setcomp_6', dedent('''\ + a = {(x, y) for x in range(10) for y in range(10) if y > x} + print(a) + ''')), + ('setcomp_7', dedent('''\ + a = {(x, y) for x in range(10) if x > 2 for y in range(10) if y > x} + print(a) + ''')), +] + +gencomp_materials = [ + ('gencomp_1', dedent('''\ + a = (x for x in range(10)) + print(list(a)) + ''')), + ('gencomp_2', dedent('''\ + a = (x for x in range(10) if x > 2) + print(list(a)) + ''')), + ('gencomp_2_1', dedent('''\ + a = (x for x in range(10) if x < 6) + print(list(a)) + ''')), + ('gencomp_3', dedent('''\ + a = (x for x in range(10) if x > 2 if x % 3 == 0) + print(list(a)) + ''')), + ('gencomp_4', dedent('''\ + a = ((x, y) for x in range(3) for y in range(3)) + print(list(a)) + ''')), + ('gencomp_5', dedent('''\ + a = ((x, y) for x in range(10) if x > 2 for y in range(10)) + print(list(a)) + ''')), + ('gencomp_6', dedent('''\ + a = ((x, y) for x in range(10) for y in range(10) if y > x) + print(list(a)) + ''')), + ('gencomp_7', dedent('''\ + a = ((x, y) for x in range(10) if x > 2 for y in range(10) if y > x) + print(list(a)) + ''')), +] + +dictcomp_materials = [ + ('dictcomp_1', dedent('''\ + a = {x: x for x in range(10)} + print(a) + ''')), + ('dictcomp_2', dedent('''\ + a = {x: x for x in range(10) if x > 2} + print(a) + ''')), + ('dictcomp_2_1', dedent('''\ + a = {x: x for x in range(10) if x < 6} + print(a) + ''')), + ('dictcomp_3', dedent('''\ + a = {x: x for x in range(10) if x > 2 if x % 3 == 0} + print(a) + ''')), + ('dictcomp_4', dedent('''\ + a = {x: y for x in range(3) for y in range(3)} + print(a) + ''')), + ('dictcomp_5', dedent('''\ + a = {x: y for x in range(10) if x > 2 for y in range(10)} + print(a) + ''')), + ('dictcomp_6', dedent('''\ + a = {x: y for x in range(10) for y in range(10) if y > x} + print(a) + ''')), + ('dictcomp_7', dedent('''\ + a = {x: y for x in range(10) if x > 2 for y in range(10) if y > x} + print(a) + ''')), +] + +namedexpr_materials = [ + ('namedexpr_1', dedent('''\ + (a := 2) + print(a) + ''')), + ('namedexpr_2', dedent('''\ + x = (a := 2) + print(a, x) + ''')), +] + +boolop_materials = [ + ('boolop_1', dedent('''\ + a = 0 or 1 + print(a) + ''')), + ('boolop_2', dedent('''\ + a = 0 and 1 + print(a) + ''')), + ('boolop_3', dedent('''\ + a = 0 or False or True + print(a) + ''')), + ('boolop_4', dedent('''\ + a = 1 and True and 'abc' + print(a) + ''')), +] + +binop_materials = [ + ('binop_1', dedent('''\ + a = 1 + b = 2 + x = a + b + 5 + int('2') + print(x) + print(a * b) + print( b / a) + print(a * b * 5) + print(10 / a / b) + print(17 % 5) + print(7 >> 2) + print(7 << 2) + print(2 ** 5) + print(2 ^ 10) + print(2 | 9) + print(2 & 9) + ''')), +] + +unaryop_materials = [ + ('unaryop_1', dedent('''\ + a = 1 + print(not a) + print(-a) + print(+a) + print(~a) + ''')), +] + +lambda_materials = [ + ('lambda_1', dedent('''\ + a = lambda x: (x + 2) * 3 + print(a(3)) + ''')), +] + +ifexp_materials = [ + ('ifexp_1', dedent('''\ + x = 1 + a = 'ok' if x > 0 else 'yes, else' + print(a) + ''')), + ('ifexp_2', dedent('''\ + x = 1 + a = 'ok' if x < 0 else 'yes else' + print(a) + ''')), +] + +dict_materials = [ + ('dict_1', dedent('''\ + x = {'a': 1, 'b': 2} + print(x) + ''')), + ('dict_2', dedent('''\ + f = {'c': 3} + x = {'a': 1, **f, 'b': 2} + print(x) + ''')), + ('dict_2', dedent('''\ + f = {'c': 3} + g = {'d': 3} + x = {'a': 1, **f, 'b': 2, **g} + print(x) + ''')), +] + +list_materials = [ + ('list_1', dedent('''\ + c = 5 + a = [1, 2, c] + print(a) + ''')), + ('list_2', dedent('''\ + a = [1, 2] + x = [3, 4, *a] + print(x) + ''')), + ('list_3', dedent('''\ + a = [1, 2] + x = [*a, 3, 4, *a, 5, 6] + print(x) + ''')), +] + +tuple_materials = [ + ('tuple_1', dedent('''\ + c = 5 + a = 1, 2, c + print(a) + ''')), + ('tuple_2', dedent('''\ + a = [1, 2] + x = 3, 4, *a + print(x) + ''')), + ('tuple_2_1', dedent('''\ + a = 1, 2 + x = 3, 4, *a + print(x) + ''')), + ('tuple_3', dedent('''\ + a = 1, 2 + x = *a, 3, 4, *a, 5, 6 + print(x) + ''')), +] + +set_materials = [ + ('set_1', dedent('''\ + c = {5} + a = {1, 2} + print(a, c) + ''')), + ('set_2', dedent('''\ + a = [1, 2] + x = {1, 3, 4, *a} + print(x) + ''')), + ('set_2_1', dedent('''\ + a = 1, 2 + x = {3, 4, *a} + print(x) + ''')), + ('set_2_2', dedent('''\ + a = {1, 2} + x = {3, 4, *a} + print(x) + ''')), + ('set_3', dedent('''\ + a = 1, 2 + x = {*a, 3, 4, *a, 5, 6} + print(x) + ''')), +] + +compare_materials = [ + ('compare_1', dedent('''\ + x = 1 > 2 + print(x) + ''')), + ('compare_1_1', dedent('''\ + x = 2 >= 1 + print(x) + ''')), + ('compare_1_2', dedent('''\ + x = 1 < 2 + print(x) + ''')), + ('compare_1_3', dedent('''\ + x = 1 <= 2 + print(x) + ''')), + ('compare_1_4', dedent('''\ + x = (1 < 2) is True + print(x) + ''')), + ('compare_1_5', dedent('''\ + x = (1 < 2) is not True + print(x) + ''')), + ('compare_1_6', dedent('''\ + x = 2 in (1, 2, 3) + print(x) + ''')), + ('compare_1_7', dedent('''\ + x = 2 not in (1, 2, 3) + print(x) + ''')), + ('compare_2', dedent('''\ + x = 5 > 4 > 3 + print(x) + ''')), + ('compare_3', dedent('''\ + x = (5 > 4) > 3 + print(x) + ''')), +] + +joinedstr_materials = [ + ('joinedstr_1', dedent('''\ + x = f"" + print(x) + ''')), + ('joinedstr_1_1', dedent('''\ + x = f"ok" + print(x) + ''')), + ('joinedstr_1_2', dedent('''\ + a = "ok" + x = f"{a}" + print(x) + ''')), + ('joinedstr_2', dedent('''\ + a = 5.82892 + x = f"sin({a}) is {float(a):.3}" + print(x) + ''')), + ('joinedstr_3', dedent('''\ + a = 60.8999 + b = 'ok' + x = f"{a}{b}sin({a}) is {a:.3}" + print(x) + ''')), +] + +try_materials = [ + ('try_1', dedent('''\ + try: + 1 / 0 + except Exception: + print('try ok') + ''')), + ('try_2', dedent('''\ + try: + 1 / 0 + except Exception as e: + print('try ok', e) + ''')), +] + +with_materials = [ + ('with_1', dedent('''\ + class cm (object): + def __enter__(self): + pass + def __exit__(self, type, value, traceback): + pass + with cm(): + print('with ok') + ''')), +] + +yield_materials = [ + ('yield_1', dedent('''\ + def gen(): + for i in range(10): + yield i + print(list(gen())) + ''')), + ('yield_2', dedent('''\ + def gen(): + a = 3 + b = 4 + c = 5 + yield from (a, b, c) + print(list(gen())) + ''')), +] + +match_materials = [ + ('match_1', dedent('''\ + flag = False + match (100, 200): + case (100, 300): # Mismatch: 200 != 300 + print('Case 1') + case (100, 200) if flag: # Successful match, but guard fails + print('Case 2') + case (100, y): # Matches and binds y to 200 + print(f'Case 3, y: {y}') + case _: # Pattern not attempted + print('Case 4, I match anything!') + ''')), +] + +cell_materials = [ + ('cell_1', dedent('''\ + def test_cell_main(): + a = 12 + def test_cell(b): + print('cell is', a) + print('arg is', b) + test_cell('9') + test_cell_main() + ''')), +] + + +script_materials = itertools.chain( + expr_call_materials, + assign_materials, + augassign_materials, + annassign_materials, + delete_materials, + for_materials, + while_materials, + if_materials, + listcomp_materials, + setcomp_materials, + gencomp_materials, + dictcomp_materials, + namedexpr_materials, + boolop_materials, + binop_materials, + unaryop_materials, + lambda_materials, + ifexp_materials, + dict_materials, + list_materials, + tuple_materials, + set_materials, + compare_materials, + joinedstr_materials, + try_materials, + with_materials, + yield_materials, + match_materials, + cell_materials, +) +# script_materials = cell_materials + + +locals_materials = [ +] + +super_materials = [ +] + +samename_materials = [ + ('samename_1', dedent('''\ + def gen(): + a = 3 + b = 4 + c = 5 + return a, b, c + print(gen()) + + def gen(): + i = 1 + return i + print(gen()) + ''')), +] + +immortal_refcnt_materials = [ +] + +async_materials = [ +] + +script_catalog = { + 'locals': locals_materials, + 'super': super_materials, + 'samename': samename_materials, + 'async': async_materials, + 'immortal_refcnt': immortal_refcnt_materials, +} + + +def iter_scripts(catalog=None): + """生成用于测试各种 Python 功能的脚本 + + 参数: + catalog (str): tpl_catalog 或者 script_catalog 中的分类名称 + + 生成: (name, source) + + name 是脚本名称,没有后缀 `.py` + source 是脚本内容 + + """ + tplinfo = tpl_catalog.get(catalog) + if tplinfo: + tpl, level = tplinfo + if level: + col = level * INDENT + prefix = ' ' * col + for name, source in script_materials: + yield name, tpl.substitute( + body=indent(source, prefix)[col:] if level else source) + else: + materials = script_catalog.get(catalog, []) + yield from materials From 7b21ffcd745ae0d766ba9f648354a192e0c9400b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 7 Dec 2025 12:38:01 +0800 Subject: [PATCH 294/337] Fix test cases issues --- tests.9/genscript.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests.9/genscript.py b/tests.9/genscript.py index 4a4bc364..c2fb74e0 100755 --- a/tests.9/genscript.py +++ b/tests.9/genscript.py @@ -12,11 +12,15 @@ # import itertools +import sys from string import Template from textwrap import dedent, indent +PYMAJOR, PYMINOR = sys.version_info[:2] + + INDENT = 4 """int: Python 脚本的缩进宽度""" @@ -847,9 +851,12 @@ def test_cell(b): try_materials, with_materials, yield_materials, - match_materials, cell_materials, ) + +if PYMINOR >= 10: + script_materials.extend(match_materials) + # script_materials = cell_materials From 86522d630cfbc8b65b7073528f643a0ad11fed33 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 7 Dec 2025 13:34:58 +0800 Subject: [PATCH 295/337] Add new testcases for v9 scripts --- tests.9/accept_test.py | 77 ++++++++++++++++----- tests.9/{genscript.py => script_factory.py} | 2 +- 2 files changed, 62 insertions(+), 17 deletions(-) rename tests.9/{genscript.py => script_factory.py} (99%) diff --git a/tests.9/accept_test.py b/tests.9/accept_test.py index adcc8908..249dc21f 100755 --- a/tests.9/accept_test.py +++ b/tests.9/accept_test.py @@ -7,7 +7,7 @@ from test.support import script_helper -import genscript as script_generator +from script_factory import script_generator class BaseTestCase(unittest.TestCase): @@ -23,7 +23,7 @@ def setUp(self): shutil.rmtree(self.local_path, ignore_errors=True) def tearDown(self): - shutil.rmtree(self.local_path, ignore_errors=True) + pass def assert_python_ok(self, *args): kwargs = { @@ -53,29 +53,74 @@ def build_v9_script(self, script, target): script_helper.assert_python_ok(*args, **kwargs) return os.path.join(self.default_output, os.path.basename(script)) + def _test_target(self, target, catalog=None): + for script in self.iter_scripts(catalog): + with self.subTest(script=script): + expected = self.assert_python_ok(script) + obfscript = self.build_v9_script(script, target) + self.verify_script_pass(obfscript, expected) + def make_script(self, source, name='foo'): return script_helper.make_script(self.work_path, name, source) - def iter_scripts(self, catalog=''): - path = os.path.join('samples', catalog) - if os.path.exists(path): - with os.scandir(path) as it: - for entry in it: - if entry.name.endswith('.py') and entry.is_file(): - yield entry.path + def iter_samples(self): + path = os.path.join(self.work_path, 'samples') + with os.scandir(path) as it: + for entry in it: + if entry.name.endswith('.py') and entry.is_file(): + yield entry.path - for name, source in script_generator.iter_scripts(catalog): - yield self.make_script(source, name=name) + def iter_scripts(self, catalog): + if catalog: + for name, source in script_generator(catalog): + yield self.make_script(source, name=name) + else: + return self.iter_samples() class UnitTestCases(BaseTestCase): + def test_mini_mode(self): + self._test_target('mini') + self._test_target('mini', 'modules') + self._test_target('mini', 'functions') + self._test_target('mini', 'methods') + def test_vmc_mode(self): - for script in self.iter_scripts('modules'): - with self.subTest(script=script): - expected = self.assert_python_ok(script) - vmcscript = self.build_v9_script(script, 'vmc') - self.verify_script_pass(vmcscript, expected) + self._test_target('vmc') + self._test_target('vmc', 'modules') + self._test_target('vmc', 'functions') + self._test_target('vmc', 'methods') + + def test_ecc_mode(self): + self._test_target('ecc') + self._test_target('ecc', 'modules') + self._test_target('ecc', 'functions') + self._test_target('ecc', 'methods') + + def test_rft_mode(self): + self._test_target('rft') + self._test_target('rft', 'modules') + self._test_target('rft', 'functions') + self._test_target('rft', 'methods') + + def test_mini_rft_mode(self): + self._test_target('mini-rft') + self._test_target('mini-rft', 'modules') + self._test_target('mini-rft', 'functions') + self._test_target('mini-rft', 'methods') + + def test_vmc_rft_mode(self): + self._test_target('vmc-rft') + self._test_target('vmc-rft', 'modules') + self._test_target('vmc-rft', 'functions') + self._test_target('vmc-rft', 'methods') + + def test_ecc_rft_mode(self): + self._test_target('ecc-rft') + self._test_target('ecc-rft', 'modules') + self._test_target('ecc-rft', 'functions') + self._test_target('ecc-rft', 'methods') if __name__ == '__main__': diff --git a/tests.9/genscript.py b/tests.9/script_factory.py similarity index 99% rename from tests.9/genscript.py rename to tests.9/script_factory.py index c2fb74e0..5f40508a 100755 --- a/tests.9/genscript.py +++ b/tests.9/script_factory.py @@ -897,7 +897,7 @@ def gen(): } -def iter_scripts(catalog=None): +def script_generator(catalog=None): """生成用于测试各种 Python 功能的脚本 参数: From b8995dc13683908d88ae1a677b15283c99a60ecc Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 8 Dec 2025 16:49:59 +0800 Subject: [PATCH 296/337] Check free-threading Python --- src/cli/__main__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index 1b132a8b..f31bcb76 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -767,6 +767,9 @@ def main_entry(argv): if not (x == 3 and y > 6 and y < 16): raise CliError('Python %s.%s is not supported' % (x, y)) + if ctx.py_gil_disabled: + raise CliError('Pyarmor does not support free-threading Python') + if args.version: print_version(ctx) parser.exit() From fcd7dfe7ec18fab550339e32c87388de1ec0b71a Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 8 Dec 2025 16:50:33 +0800 Subject: [PATCH 297/337] Add new target --ecc-nogil/--ecc-rft-nogil --- src/cli/command.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/cli/command.py b/src/cli/command.py index 5b14690b..268f8a8a 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -170,6 +170,17 @@ def build_parser(self, subparsers): action="store_const", const='ecc', help='generate scripts by combining --rft and --ecc' ) + group.add_argument( + '--ecc-nogil', dest='target', default='std', + action="store_const", const='ecc', + help='genetate ECC scripts for free-threading Python' + ) + group.add_argument( + '--ecc-rft-nogil', dest='target', default='std', + action="store_const", const='ecc', + help=('generate scripts by combining --rft and --ecc' + ' for free-threading Python') + ) group.add_argument( '--list', dest='target', default='std', action="store_const", const='list', From b11e887f7f85e05126db0d7d0c2700a29c8ff7bd Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 8 Dec 2025 22:11:27 +0800 Subject: [PATCH 298/337] Init nogil test --- tests.9/Makefile | 66 ++++++++++++----- tests.9/accept_test.py | 2 +- tests.9/accept_test_nogil.py | 135 +++++++++++++++++++++++++++++++++++ tests.9/script_factory.py | 14 ++-- 4 files changed, 191 insertions(+), 26 deletions(-) create mode 100755 tests.9/accept_test_nogil.py diff --git a/tests.9/Makefile b/tests.9/Makefile index 249b2c96..f928a912 100644 --- a/tests.9/Makefile +++ b/tests.9/Makefile @@ -4,7 +4,7 @@ # 1. Basic usage, test all pythons in this platform # # make test -# make test-nogil +# make nogil-test # # 2. Test one python version # @@ -12,13 +12,19 @@ # # 3. Test free-threaded version # -# make test-3.14 NOGIL=y +# make nogil-test-3.13 +# +# Prerequisites: +# +# - Python has been installed in the path "venv" +# - Pyarmor has been registerred (Pro features) # HOSTS := darwin.x86_64 darwin.arm64 linux.x86_64 linux.aarch64 windows.x86_64 UNAME ?= $(shell uname) ARCH ?= $(shell arch) -PYVERS ?= 3.9 3.10 3.11 3.12 3.13 3.14 3.15 +PYVERS ?= 3.9 3.10 3.11 3.12 3.13 3.14 +PYVERS_T ?= 3.13 3.14 PYVER ?= $(word 1,${PYVERS}) ifneq ($(findstring ${PYVER},${PYVERS}),${PYVER}) @@ -28,29 +34,47 @@ endif ifeq ($(findstring Linux,$(UNAME)),Linux) ifeq ($(ARCH),x86_64) HOST ?= linux.x86_64 + PYTHON := /usr/local/bin/python${PYVER} else ifeq ($(ARCH),aarch64) HOST ?= linux.aarch64 + PYTHON := /usr/bin/python${PYVER} else HOST ?= linux.x86 + PYTHON := /usr/local/bin/python${PYVER} endif + + PYTHON_T := ${PYTHON}t + else ifeq ($(findstring Darwin,$(UNAME)),Darwin) ifeq ($(ARCH),arm64) HOST ?= darwin.arm64 + PYTHON := venv/${PYVER}/bin/python${PYVER} + PYTHON_T := venv/${PYVER}t/bin/python${PYVER} else HOST ?= darwin.x86_64 + PYTHON := venv/${PYVER}/bin/python${PYVER} + PYTHON_T := venv/${PYVER}t/bin/python${PYVER} endif + else ifeq ($(ARCH),x86_64) HOST ?=windows.x86_64 + PYTHON := venv/${PYVER}/Scripts/python.exe + PYTHON_T := venv/${PYVER}t/Scripts/python.exe else HOST ?=windows.x86 + PYTHON := venv/${PYVER}/Scripts/python.exe + PYTHON_T := venv/${PYVER}t/Scripts/python.exe endif endif -ifeq ($(findstring windows,${HOST}),windows) - PYTHON := venv/bin/python$(subst .,,${PYVER}) +PIPINST = install --force-reinstall --disable-pip-version-check +ifeq (${HOST},linux.aarch64) + PIP := ${PYTHON} pip.pyz ${PIPINST} + PIP_T := ${PYTHON_T} pip.pyz ${PIPINST} else - PYTHON := venv/bin/python${PYVER} + PIP := ${PYTHON} -m pip ${PIPINST} + PIP_T := ${PYTHON_T} -m pip ${PIPINST} endif $(info python is ${PYTHON}) @@ -72,6 +96,7 @@ pkgpath := ${workpath}/pkgdist testpkg := $(wildcard ${pkgpath}/pyarmor_cli-*-none-any.whl) corepkg := $(wildcard ${pkgpath}/pyarmor_cli_core-*-${PYTAG}-*-${PLATFORM}.whl) minipkg := $(wildcard ${pkgpath}/pyarmor_mini-*-${PYTAG}-${PYTAG}-${PLATFORM}.whl) +minipkg_t := $(wildcard ${pkgpath}/pyarmor_mini-*-${PYTAG}-${PYTAG}t-${PLATFORM}.whl) ifndef testpkg $(error no test package found) @@ -85,34 +110,41 @@ ifndef minipkg $(error no mini package found) endif -$(info got test package: $(basename ${testpkg})) -$(info got core package: $(basename ${corepkg})) -$(info got mini package: $(basename ${minipkg})) +$(info got test package: $(notdir ${testpkg})) +$(info got core package: $(notdir ${corepkg})) +$(info got mini package: $(notdir ${minipkg})) -PIPOPTIONS := --force-reinstall --disable-pip-version-check +.PHONY: all test nogil-test test-* notil-test-* install clean -.PHONY: all test test-nogil test-* install clean - -all: test test-nogil +all: test nogil-test test: for ver in ${PYVERS} ; do \ make test-$$ver ; \ done -test-nogil: - for ver in ${PYVERS} ; do \ - make test-$$ver NOGIL=y ; \ +nogil-test: + for ver in ${PYVERS_T} ; do \ + make nogil-test-$$ver ; \ done install: - ${PYTHON} -m pip install ${PIPOPTIONS} ${corepkg} ${testpkg} ${minipkg} + ${PIP} ${corepkg} ${testpkg} ${minipkg} test-%: PYVER=$(subst test-,,$@) make install run-test +nogil-install: + ${PIP_T} ${minipkg_t} + +nogil-test-%: + PYVER=$(subst nogil-test-,,$@) make install nogil-install run-nogil-test + run-test: ${PYTHON} ${workpath}/accept_test.py +run-nogil-test: + PYTHON_NOGIL=${PYTHON_T} ${PYTHON} ${workpath}/accept_test_nogil.py + clean: rm -rf ${workpath} __pycache__ testv9.tar.gz diff --git a/tests.9/accept_test.py b/tests.9/accept_test.py index 249dc21f..0698e8fb 100755 --- a/tests.9/accept_test.py +++ b/tests.9/accept_test.py @@ -75,7 +75,7 @@ def iter_scripts(self, catalog): for name, source in script_generator(catalog): yield self.make_script(source, name=name) else: - return self.iter_samples() + yield from self.iter_samples() class UnitTestCases(BaseTestCase): diff --git a/tests.9/accept_test_nogil.py b/tests.9/accept_test_nogil.py new file mode 100755 index 00000000..8dd7003c --- /dev/null +++ b/tests.9/accept_test_nogil.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +import logging +import os +import shutil +import unittest + +from test.support import script_helper + +from script_factory import script_generator + + +class BaseTestCase(unittest.TestCase): + + work_path = os.path.abspath(os.path.dirname(__file__)) + + @classmethod + def setUpClass(cls): + cls.python_nogil = os.path.abspath(os.getenv('PYTHON_NOGIL')) + cls.local_path = os.path.join(cls.work_path, '.pyarmor') + cls.default_output = os.path.join(cls.work_path, 'dist') + + def setUp(self): + shutil.rmtree(self.local_path, ignore_errors=True) + + def tearDown(self): + pass + + def assert_python_ok(self, *args): + kwargs = { + '__isolated': False + } + return script_helper.assert_python_ok(*args, **kwargs) + + def assert_python_failure(self, *args): + kwargs = { + '__isolated': False + } + return script_helper.assert_python_failure(*args, **kwargs) + + def verify_script_nogil_pass(self, script, expected): + from subprocess import check_output + assert os.path.exists(self.python_nogil), self.python_nogil + stdout = check_output([self.python_nogil, script]) + self.assertEqual(expected[1], stdout) + + def build_v9_script(self, script, target): + kwargs = { + '__isolated': False, + '__cwd': self.work_path, + } + args = ['-m', 'pyarmor.cli', 'init', '-e', os.path.abspath(script)] + script_helper.assert_python_ok(*args, **kwargs) + + args = ['-m', 'pyarmor.cli', 'build', '--' + target] + script_helper.assert_python_ok(*args, **kwargs) + return os.path.join(self.default_output, os.path.basename(script)) + + def _test_target(self, target, catalog=None): + for script in self.iter_scripts(catalog): + with self.subTest(script=script): + expected = self.assert_python_ok(script) + obfscript = self.build_v9_script(script, target) + self.verify_script_nogil_pass(obfscript, expected) + + def make_script(self, source, name='foo'): + return script_helper.make_script(self.work_path, name, source) + + def iter_samples(self): + path = os.path.join(self.work_path, 'samples') + with os.scandir(path) as it: + for entry in it: + if entry.name.endswith('.py') and entry.is_file(): + yield entry.path + + def iter_scripts(self, catalog): + if catalog: + for name, source in script_generator(catalog): + yield self.make_script(source, name=name) + else: + yield from self.iter_samples() + + +class UnitTestCases(BaseTestCase): + + def test_mini_mode(self): + self._test_target('mini') + self._test_target('mini', 'modules') + self._test_target('mini', 'functions') + self._test_target('mini', 'methods') + + def test_vmc_mode(self): + self._test_target('vmc') + self._test_target('vmc', 'modules') + self._test_target('vmc', 'functions') + self._test_target('vmc', 'methods') + + def test_ecc_mode(self): + self._test_target('ecc-nogil') + self._test_target('ecc-nogil', 'modules') + self._test_target('ecc-nogil', 'functions') + self._test_target('ecc-nogil', 'methods') + + def test_rft_mode(self): + self._test_target('rft') + self._test_target('rft', 'modules') + self._test_target('rft', 'functions') + self._test_target('rft', 'methods') + + def test_mini_rft_mode(self): + self._test_target('mini-rft') + self._test_target('mini-rft', 'modules') + self._test_target('mini-rft', 'functions') + self._test_target('mini-rft', 'methods') + + def test_vmc_rft_mode(self): + self._test_target('vmc-rft') + self._test_target('vmc-rft', 'modules') + self._test_target('vmc-rft', 'functions') + self._test_target('vmc-rft', 'methods') + + def test_ecc_rft_mode(self): + self._test_target('ecc-nogil-rft') + self._test_target('ecc-nogil-rft', 'modules') + self._test_target('ecc-nogil-rft', 'functions') + self._test_target('ecc-nogil-rft', 'methods') + + +if __name__ == '__main__': + logging.getLogger().addHandler(logging.NullHandler()) + + loader = unittest.TestLoader() + # loader.testMethodPrefix = 'test_nogil' + suite = loader.loadTestsFromTestCase(UnitTestCases) + result = unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tests.9/script_factory.py b/tests.9/script_factory.py index 5f40508a..999b7b21 100755 --- a/tests.9/script_factory.py +++ b/tests.9/script_factory.py @@ -5,9 +5,9 @@ # 使用方法: # # from test.support import script_helper -# from driver import generator +# from script_factory import script_generator # -# for name, source in generator.iter_scripts(''): +# for name, source in script_generator('functions'): # script_helper.make_script(source, name=name) # @@ -854,12 +854,6 @@ def test_cell(b): cell_materials, ) -if PYMINOR >= 10: - script_materials.extend(match_materials) - -# script_materials = cell_materials - - locals_materials = [ ] @@ -918,6 +912,10 @@ def script_generator(catalog=None): for name, source in script_materials: yield name, tpl.substitute( body=indent(source, prefix)[col:] if level else source) + # if PYMINOR >= 10: + # for name, source in match_materials: + # yield name, tpl.substitute( + # body=indent(source, prefix)[col:] if level else source) else: materials = script_catalog.get(catalog, []) yield from materials From b4c9c8a88109303b2100bde82c15c03aa4ef25b1 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 8 Dec 2025 22:11:46 +0800 Subject: [PATCH 299/337] Add nogil target for build command --- src/cli/command.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index 268f8a8a..bda54a42 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -167,17 +167,17 @@ def build_parser(self, subparsers): ) group.add_argument( '--ecc-rft', dest='target', default='std', - action="store_const", const='ecc', + action="store_const", const='ecc-rft', help='generate scripts by combining --rft and --ecc' ) group.add_argument( '--ecc-nogil', dest='target', default='std', - action="store_const", const='ecc', + action="store_const", const='ecc-nogil', help='genetate ECC scripts for free-threading Python' ) group.add_argument( '--ecc-rft-nogil', dest='target', default='std', - action="store_const", const='ecc', + action="store_const", const='ecc-rft-nogil', help=('generate scripts by combining --rft and --ecc' ' for free-threading Python') ) From 3837e87c1df3a32bea51456b6968037134f11b72 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 8 Dec 2025 23:27:37 +0800 Subject: [PATCH 300/337] Change version to 9.2.3 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 8cce89d1..4daa6189 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.2' # The full version, including alpha/beta/rc tags. -release = '9.2.2' +release = '9.2.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 6e22c1ed..70094120 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.2.2' +__VERSION__ = '9.2.3' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index bd758d67..eb6e0e5a 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 2 -patch = 2 +patch = 3 ;; Compatible core version for package: pyarmor.cli.core cli.core = 8.1.0 diff --git a/src/config.py b/src/config.py index d026e371..27b1a824 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.2.2' +version = '9.2.3' # The corresponding version of pytransform.so core_version = 'r52.6' From 0107f9254a44602094bedfb82cf51cf1c5abb9da Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sun, 14 Dec 2025 20:36:09 +0800 Subject: [PATCH 301/337] Fix issue #2266 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index e5bbb2f4..c38edc18 100644 --- a/setup.py +++ b/setup.py @@ -118,10 +118,10 @@ entry_points={ 'console_scripts': [ - 'pyarmor=pyarmor.pyarmor:main_entry_8', + 'pyarmor=pyarmor.cli.__main__:main', 'pyarmor-auth=pyarmor.cli.docker:main', 'pyarmor-7=pyarmor.pyarmor:main_entry', - 'pyarmor-8=pyarmor.cli.__main__:main', + 'pyarmor-8=pyarmor.pyarmor:main_entry_8', ], }, ) From 2eb7a9389cd68aa892fa49f7de31ca221a4643f6 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 16 Dec 2025 09:37:42 +0800 Subject: [PATCH 302/337] Add win10 test server --- tests.8/Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests.8/Makefile b/tests.8/Makefile index a5780730..defa9700 100644 --- a/tests.8/Makefile +++ b/tests.8/Makefile @@ -29,7 +29,12 @@ else ifeq ($(findstring Darwin,$(UNAME)),Darwin) PYVERS := ${ALL_PYVERS} PYTHONBIN := /usr/local/bin/pythonXY endif -else +else ifeq (${HOSTNAME},584VVL9L9XIZV8H) + # This is win10 test server + HOST ?=windows.x86_64 + PYVERS := 3.9 3.10 3.11 3.12 3.13 3.14 + PYTHONBIN := C:/Users/Administrator/AppData/Local/Programs/Python/PythonXY/python.exe +else ifeq (${HOSTNAME},WIN7-1611171239) ifeq (${HOST},windows.x86) PYVERS := 3.7 3.8 PYTHONBIN := C:/PythonXY-32/python From 3ecfb543ecc97b4457c6891d85745e686046bdb1 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 16 Dec 2025 10:57:27 +0800 Subject: [PATCH 303/337] Fix win10 testcase issues --- tests.8/Makefile | 12 +++++++----- tests.8/accept_test.py | 8 +++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/tests.8/Makefile b/tests.8/Makefile index defa9700..d065ae02 100644 --- a/tests.8/Makefile +++ b/tests.8/Makefile @@ -33,7 +33,7 @@ else ifeq (${HOSTNAME},584VVL9L9XIZV8H) # This is win10 test server HOST ?=windows.x86_64 PYVERS := 3.9 3.10 3.11 3.12 3.13 3.14 - PYTHONBIN := C:/Users/Administrator/AppData/Local/Programs/Python/PythonXY/python.exe + PYTHONBIN := ~/v9-test/venv/XY/Scripts/python.exe else ifeq (${HOSTNAME},WIN7-1611171239) ifeq (${HOST},windows.x86) PYVERS := 3.7 3.8 @@ -58,7 +58,9 @@ ifneq ($(findstring ${PYVER},${PYVERS}),${PYVER}) $(error "${PYVER}" not in supported Python versions "${PYVERS}") endif -ifeq (${HOST},windows.x86_64) +ifeq (${HOSTNAME},584VVL9L9XIZV8H) + PYTHON ?= $(subst XY,${PYVER},${PYTHONBIN}) +else ifeq (${HOST},windows.x86_64) PYTHON ?= $(subst XY,$(subst .,,${PYVER}),${PYTHONBIN}) else ifeq (${HOST},windows.x86) PYTHON ?= $(subst XY,$(subst .,,${PYVER}),${PYTHONBIN}) @@ -89,7 +91,7 @@ whlpath := $(shell (test -d ${whlpath} && echo ${whlpath}) || echo ./pkgdist) pkgfile := $(word 1,$(shell cd ${pkgpath}; ls -t pyarmor-*.tar.gz)) testver := $(subst pyarmor-,,$(subst .tar.gz,,${pkgfile})) coreprefix := pyarmor_cli_core- -coresuffix := -cp38-none-macosx_10_14_x86_64.whl +coresuffix := -cp38-cp38-macosx_10_14_x86_64.whl corever=$(subst ${coreprefix},,$(subst ${coresuffix},,$(shell cd ${whlpath}; ls -t ${coreprefix}*${coresuffix}))) $(info got test package file: ${pkgfile}) @@ -112,8 +114,8 @@ install-%: @tar xzf ${pkgpath}/pyarmor-${testver}.tar.gz @mv pyarmor-${testver}/pyarmor . @rm -rf pyarmor-${testver} - @unzip ${whlpath}/pyarmor_cli_core-${corever}-cp$(subst .,,$(subst install-,,$@))-none-${platform}.whl > /dev/null - @(themida=../pyarmor_cli_core_themida-${corever}-cp$(subst .,,$(subst install-,,$@))-none-any.whl && test -f $$themida && unzip $$themida > /dev/null || echo no themida $$themida) + @unzip ${whlpath}/pyarmor_cli_core-${corever}-cp$(subst .,,$(subst install-,,$@))-cp$(subst .,,$(subst install-,,$@))-${platform}.whl > /dev/null + @(themida=../pyarmor_cli_core_themida-${corever}-cp$(subst .,,$(subst install-,,$@))-cp$(subst .,,$(subst install-,,$@))-any.whl && test -f $$themida && unzip $$themida > /dev/null || echo no themida $$themida) @rm -rf pyarmor_cli_core-${corever}.dist-info atest: diff --git a/tests.8/accept_test.py b/tests.8/accept_test.py index 344c4ef5..fc087f72 100755 --- a/tests.8/accept_test.py +++ b/tests.8/accept_test.py @@ -3,7 +3,6 @@ import logging import os import shutil -import sys import time import unittest @@ -11,6 +10,9 @@ from test.support import script_helper +is_win7 = os.getenv('HOSTNAME', '').startswith('WIN7-') + + def metricmethod(func): if not hasattr(time, 'process_time'): time.process_time = time.clock @@ -227,13 +229,13 @@ def test_bind_multiple_devices(self): self.pyarmor_gen(args) self.verify_dist_foo() - @unittest.skipUnless(sys.platform.startswith('win'), 'only for windows') + @unittest.skipUnless(is_win7, 'only for windows 7') def test_themida_script(self): args = ['g', '--enable-themida', 'samples/foo.py'] self.pyarmor_gen(args) self.verify_dist_foo() - @unittest.skipUnless(sys.platform.startswith('win'), 'only for windows') + @unittest.skipUnless(is_win7, 'only for windows 7') def test_themida_bcc(self): args = ['g', '--enable-themida', '--enable-bcc', 'samples/foo.py'] self.pyarmor_gen(args) From b11b186d839cd98903179b2bff404e5ee2a73ca8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 16 Dec 2025 17:50:43 +0800 Subject: [PATCH 304/337] Change version to 9.2.4 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 4daa6189..251ce1f5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.2' # The full version, including alpha/beta/rc tags. -release = '9.2.3' +release = '9.2.4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 70094120..608527fb 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.2.3' +__VERSION__ = '9.2.4' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index eb6e0e5a..b3b0d8f5 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 2 -patch = 3 +patch = 4 ;; Compatible core version for package: pyarmor.cli.core cli.core = 8.1.0 diff --git a/src/config.py b/src/config.py index 27b1a824..54542896 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.2.3' +version = '9.2.4' # The corresponding version of pytransform.so core_version = 'r52.6' From 1fce186ceacb2bba8ac686e73bd453d3b315c694 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 17 Dec 2025 08:42:13 +0800 Subject: [PATCH 305/337] Doc how to request more CI quota --- docs/how-to/ci.rst | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 483b8c1b..96a7c1d8 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -131,9 +131,10 @@ Then execute it in the workflow:: $ pyarmor reg pyarmor-ci-8000.zip $ python3 batch.py -If merge solution doesn't work, or you don't want change the original workflow, it need extra steps. +If merge solution doesn't work, or you don't want change the original workflow, it need request more quota. -Pyarmor team need verify this project, and you need request one new CI regfile to use special license server for it. +Request More Quota +~~~~~~~~~~~~~~~~~~ The free quota is 10, 000 runs per month, exceed free quota, it need extra fees: @@ -142,6 +143,24 @@ The free quota is 10, 000 runs per month, exceed free quota, it need extra fees: - 300,000 per month, extra fees: $30 for one year - ... +The steps to request more quota + +1. Send request to Pyarmor Team (pyarmor@163.com) + + Please provide the project information, web link, etc. Pyarmor team only use these information for internal review. + + And the quota per month. For example, 100, 000 per month. + +2. Pyarmor Team send you PayPal invoice of extra fees. + + For example, if you request quota is 100, 000 per month, Pyarmor team will send you $10 invoice. One year later, when this CI license is expired and need renew, Pyarmor Team will send you $90 + $10 = $100 invoice + +3. After invoice is paid, Pyarmor Team will send you notification email + +4. You need request one new CI regfile with Pyarmor 9.2+ + + Only Pyarmor 9.2+ support this feature. + Indirect Way ============ From 74042e72fe218df8186f1da0be07191286682546 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 20 Dec 2025 11:27:38 +0800 Subject: [PATCH 306/337] Add config option `max_workers` to generate the obfuscated scripts by ProcessPoolExecutor (#2272) --- src/cli/default.cfg | 7 +++++ src/cli/generate.py | 66 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index b3b0d8f5..91953481 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -235,6 +235,13 @@ rft_mix_import_name = 0 ;; Strip leading name package strip_package_name = 1 +;; Enable multiple process builder +;; +;; 0: normal build +;; 1: use ProcessPoolExecutor with max_workers=None +;; N > 1: use ProcessPoolExecutor with max_workers=N +max_workers = 0 + ;; ;; Compatiable options ;; diff --git a/src/cli/generate.py b/src/cli/generate.py index 1d6ec65c..231409d7 100644 --- a/src/cli/generate.py +++ b/src/cli/generate.py @@ -185,7 +185,71 @@ def process(self, options, packer=None): logger.info('generate runtime files OK') logger.info('start to obfuscate scripts') - self._obfuscate_scripts() + n = self.ctx.cfg.getint('builder', 'max_workers') + async_obfuscate_scripts(self, n) if n else self._obfuscate_scripts() logger.info('obfuscate scripts OK') Pytransform3.post_build(self.ctx) + + +def async_obfuscate_scripts(builder, max_workers): + from concurrent.futures import ProcessPoolExecutor, wait + + ctx = builder.ctx + rev = ctx.version_info() + template = ctx.bootstrap_template + relative = ctx.import_prefix + pkgname = ctx.runtime_package_name + bootpath = ctx.cfg.get('builder', 'bootstrap_file') + encoding = ctx.cfg.get('builder', 'encoding') + max_workers = None if max_workers < 2 else max_workers + + plugins = [x for x in ctx.plugins if hasattr(x, 'post_script')] + + namelist = [] + futures = [] + + with ProcessPoolExecutor(max_workers=max_workers) as executor: + for res in ctx.resources + ctx.extra_resources: + logger.info('process resource "%s"', res.fullname) + name = res.name + path = builder.format_output(ctx.outputs, namelist.count(name)) + namelist.append(name) + os.makedirs(path, exist_ok=True) + + for r in res: + if not r.is_script(): + logger.info('copy data file %s', r.fullpath) + data_path = os.path.join(path, r.output_path) + os.makedirs(data_path, exist_ok=True) + shutil.copy2(r.fullpath, data_path) + continue + + logger.info('obfuscating %s', r) + futures.append(executor.submit(aysnc_generate_script, *( + r, path, ctx, template, relative, pkgname, bootpath, + rev, plugins, encoding))) + + wait(futures) + for x in futures: + x.result() + + +def aysnc_generate_script(r, path, ctx, template, relative, + pkgname, bootpath, rev, plugins, encoding): + code = Pytransform3.generate_obfuscated_script(ctx, r) + source = r.generate_output( + template, code, relative=relative, pkgname=pkgname, + bootpath=bootpath, rev=rev + ) + + fullpath = os.path.join(path, r.output_filename) + os.makedirs(os.path.dirname(fullpath), exist_ok=True) + + for plugin in plugins: + patched_source = plugin.post_script(ctx, r, source) + if patched_source: + source = patched_source + + with open(fullpath, 'w', encoding=encoding) as f: + f.write(source) From f984349a74cee9b6984329ebaa6220bb3554e190 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 20 Dec 2025 12:27:55 +0800 Subject: [PATCH 307/337] Refine notes --- src/cli/default.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 91953481..17668944 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -235,7 +235,7 @@ rft_mix_import_name = 0 ;; Strip leading name package strip_package_name = 1 -;; Enable multiple process builder +;; Enable ProcessPoolExecuter to generate the obfuscated scripts ;; ;; 0: normal build ;; 1: use ProcessPoolExecutor with max_workers=None From 439137e42571077aa1ae949583129735876bfd6e Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 25 Dec 2025 09:41:10 +0800 Subject: [PATCH 308/337] Fix pyarmor.cli.core may not in the same path with pyarmor.cli issue --- src/cli/command.py | 2 +- src/cli/generate.py | 3 ++- src/cli/register.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cli/command.py b/src/cli/command.py index bda54a42..4bd7d197 100644 --- a/src/cli/command.py +++ b/src/cli/command.py @@ -442,7 +442,7 @@ def cmd_build(self, ctx, args): logger.info('build target %s end', args.target) def _build(self, project, target, output, value=None): - from .core import Pytransform3 + from pyarmor.cli.core import Pytransform3 args = [self.ctx, target, project, output, value] m = Pytransform3.init(self.ctx) m.pre_build(args) diff --git a/src/cli/generate.py b/src/cli/generate.py index 231409d7..240a113b 100644 --- a/src/cli/generate.py +++ b/src/cli/generate.py @@ -22,8 +22,9 @@ import os import shutil +from pyarmor.cli.core import Pytransform3 + from . import logger, CliError -from .core import Pytransform3 from .resource import FileResource, PathResource diff --git a/src/cli/register.py b/src/cli/register.py index a7fcdea8..d5460e65 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -171,7 +171,7 @@ def regurl(self, ucode, product=None, rcode=None, prepare=False): return url def update_token(self): - from .core import Pytransform3 + from pyarmor.cli.core import Pytransform3 assert Pytransform3._pytransform3 is None with open(self.ctx.license_token, 'wb') as f: f.close() @@ -359,7 +359,7 @@ def _get_docker_hostname(self): logger.debug('%s:%d:%s', host, port, str(e)) def _get_machine_id(self, devflag=11): - from .core import Pytransform3 + from pyarmor.cli.core import Pytransform3 try: return Pytransform3.get_hd_info(devflag) except SystemError: From b665a83fc66d1fff21315c44d5b876bf33e168b4 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 25 Dec 2025 16:00:17 +0800 Subject: [PATCH 309/337] Update doc link to pyarmor.eke.org.cn --- README.md | 12 ++++++------ docs/_common_definitions.txt | 2 ++ docs/index.rst | 2 +- docs/questions.rst | 2 +- docs/reference/man.rst | 6 +++--- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b5cc188b..0efd9082 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Please read the [Pyarmor EULA](LICENSE). ## Getting Help -- **[Ask in learning system][askeke] or [look through check list][checklist]** +- **[Ask in learning system][learning] or [look through check list][checklist]** - **Consult the [Pyarmor Documentation][doc].** - **Check the [FAQ][faq] for answers to common questions.** - **Try the documentation [index][genindex] or the [detailed table of contents][mastertoc].** @@ -84,8 +84,8 @@ There is also one third-party learn platform * [Documentation][doc] * [Documentation 8.x](https://pyarmor.readthedocs.io/en/v8.5.12/) * [Documentation 7.x](https://pyarmor.readthedocs.io/en/v7.7/) -* [Pyarmor 9.1 new features](https://eke.dashingsoft.com/pyarmor/docs/en/index.html) -* [Pyarmor Learning System](https://eke.dashingsoft.com/pyarmor/) +* [Pyarmor 9.1 new features](https://pyarmor.eke.org.cn/v9.html) +* [Pyarmor Learning System][learning] 中文资源 @@ -93,8 +93,8 @@ There is also one third-party learn platform * [Pyarmor 在线文档](https://pyarmor.readthedocs.io/zh/latest/) * [Pyarmor 8.x 在线文档](https://pyarmor.readthedocs.io/zh/v8.5.12/) * [Pyarmor 7.x 在线文档](https://pyarmor.readthedocs.io/zh/v7.x/) -* [Pyarmor 9.1 新功能](https://eke.dashingsoft.com/pyarmor/docs/zh/index.html) -* [Pyarmor 学习系统](https://eke.dashingsoft.com/pyarmor/) +* [Pyarmor 9.1 新功能](https://pyarmor.eke.org.cn/v9.html) +* [Pyarmor 学习系统][learning] ## Changelog @@ -117,5 +117,5 @@ Make sure to read the changelog carefully before upgrading Pyarmor: [asking]: https://pyarmor.readthedocs.io/en/latest/questions.html#asking-questions-in-github [doc]: https://pyarmor.readthedocs.io/ [gurubase]: https://gurubase.io/g/pyarmor -[askeke]: https://eke.dashingsoft.com/pyarmor/ask +[learning]: https://pyarmor.eke.org.cn/ [checklist]: https://pyarmor.readthedocs.io/en/latest/reference/solutions.html diff --git a/docs/_common_definitions.txt b/docs/_common_definitions.txt index 54a97b04..bb241579 100644 --- a/docs/_common_definitions.txt +++ b/docs/_common_definitions.txt @@ -14,6 +14,8 @@ .. |Manual| replace:: https://pyarmor.readthedocs.io/ .. |Author| replace:: Jondy .. |Contact| replace:: pyarmor@163.com +.. |V9doc| replace:: https://pyarmor.eke.org.cn/v9.html +.. |V9man| replace:: https://eke.dashingsoft.com/pyarmor/docs/en/user/man.html .. |FAQ| replace:: :doc:`FAQ ` diff --git a/docs/index.rst b/docs/index.rst index 03e0cd3f..4212c393 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,7 +12,7 @@ New features introduced by Pyarmor 9.1.0 are also availabled here - https://eke.dashingsoft.com/pyarmor/docs/en/index.html + |V9doc| It includes 3 commands: `pyarmor init`, `pyarmor env`, `pyarmor build` diff --git a/docs/questions.rst b/docs/questions.rst index 1b9821d8..6ebf6167 100644 --- a/docs/questions.rst +++ b/docs/questions.rst @@ -12,7 +12,7 @@ If using :command:`pyarmor-7` or Pyarmor < 8.0, please check `Pyarmor 7.x Doc`_ .. important:: - Pyarmor team handles too many wrong usage issues, so one document :doc:`reference/solutions` has been arranged to solve this kind of issue quickly. If you aren't sure this issue is wrong usage or not, please check this doc or https://eke.dashingsoft.com/pyarmor/ask/ at first. + Pyarmor team handles too many wrong usage issues, so one document :doc:`reference/solutions` has been arranged to solve this kind of issue quickly. If you aren't sure this issue is wrong usage or not, please check this doc or https://pyarmor.eke.org.cn/ at first. Pyarmor team will mark this kind of issue as `invalid` or `documented` and close it immediately. diff --git a/docs/reference/man.rst b/docs/reference/man.rst index 1800dc11..6199b840 100644 --- a/docs/reference/man.rst +++ b/docs/reference/man.rst @@ -1042,7 +1042,7 @@ pyarmor init .. versionadded:: 9.1 -Refer to https://eke.dashingsoft.com/pyarmor/docs/en/user/man.html +Refer to |V9man| .. _pyarmor env: @@ -1051,7 +1051,7 @@ pyarmor env .. versionadded:: 9.1 -Refer to https://eke.dashingsoft.com/pyarmor/docs/en/user/man.html +Refer to |V9man| .. _pyarmor build: @@ -1060,7 +1060,7 @@ pyarmor build .. versionadded:: 9.1 -Refer to https://eke.dashingsoft.com/pyarmor/docs/en/user/man.html +Refer to |V9man| Environment Variables ===================== From da011ffbd44f80a3886c6474d31c1b41599f84f4 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 25 Dec 2025 21:29:00 +0800 Subject: [PATCH 310/337] Refine link format --- docs/_common_definitions.txt | 4 ++-- docs/index.rst | 4 +--- docs/reference/man.rst | 6 +++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/_common_definitions.txt b/docs/_common_definitions.txt index bb241579..3443dd4b 100644 --- a/docs/_common_definitions.txt +++ b/docs/_common_definitions.txt @@ -6,6 +6,8 @@ .. _Pyarmor EULA: https://github.com/dashingsoft/pyarmor/blob/master/LICENSE .. _MyCommerce Shopping: https://order.mycommerce.com/product?vendorid=200089125&productid=301044051 .. _Pyarmor Shopping Cart: https://jondy.github.io/paypal/index.html +.. _Pyarmor V9 Man: https://pyarmor.eke.org.cn/archive/v9/docs/en/user/man.html +.. _Pyarmor V9 Doc: https://pyarmor.eke.org.cn/v9.html .. |pyarmor| replace:: :doc:`pyarmor ` .. |Pyarmor| replace:: :term:`Pyarmor` @@ -14,8 +16,6 @@ .. |Manual| replace:: https://pyarmor.readthedocs.io/ .. |Author| replace:: Jondy .. |Contact| replace:: pyarmor@163.com -.. |V9doc| replace:: https://pyarmor.eke.org.cn/v9.html -.. |V9man| replace:: https://eke.dashingsoft.com/pyarmor/docs/en/user/man.html .. |FAQ| replace:: :doc:`FAQ ` diff --git a/docs/index.rst b/docs/index.rst index 4212c393..d65d72c6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,9 +10,7 @@ .. important:: - New features introduced by Pyarmor 9.1.0 are also availabled here - - |V9doc| + New features introduced by Pyarmor 9.1.0 are also in the `Pyarmor V9 Doc`_ It includes 3 commands: `pyarmor init`, `pyarmor env`, `pyarmor build` diff --git a/docs/reference/man.rst b/docs/reference/man.rst index 6199b840..17aefa4d 100644 --- a/docs/reference/man.rst +++ b/docs/reference/man.rst @@ -1042,7 +1042,7 @@ pyarmor init .. versionadded:: 9.1 -Refer to |V9man| +Refer to `Pyarmor V9 Man`_ .. _pyarmor env: @@ -1051,7 +1051,7 @@ pyarmor env .. versionadded:: 9.1 -Refer to |V9man| +Refer to `Pyarmor V9 Man`_ .. _pyarmor build: @@ -1060,7 +1060,7 @@ pyarmor build .. versionadded:: 9.1 -Refer to |V9man| +Refer to `Pyarmor V9 Man`_ Environment Variables ===================== From 451bbab4d3035bf703a52421dbba1e89fe36e48b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 21 Jan 2026 20:00:38 +0800 Subject: [PATCH 311/337] Refine error message --- src/cli/__main__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/cli/__main__.py b/src/cli/__main__.py index f31bcb76..9fbeea41 100644 --- a/src/cli/__main__.py +++ b/src/cli/__main__.py @@ -791,11 +791,19 @@ def main_entry(argv): parser.print_help() +def map_error(e): + if str(e) == 'invalid license token': + return ('invalid license token or failed to update license token. ' + 'please make sure this device is online ' + 'and can connect to Pyarmor License Server') + return e + + def main(): try: main_entry(sys.argv[1:]) except Exception as e: - logger.error(e) + logger.error(map_error(e)) log_bug(e) sys.exit(1 if isinstance(e, CliError) else 2) From 76117234f9d42f8f92f09b56359e6afb838c5231 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 23 Jan 2026 09:15:09 +0800 Subject: [PATCH 312/337] Refine error message for invalid activation file --- src/cli/register.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index d5460e65..be67d1a1 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -208,6 +208,9 @@ def parse_keyfile(self, filename): if len(line) == 192 and line.find(' ') == -1: return regname, line + logger.error('please make sure the activation file is ' + 'pure text file, the content starts with `Dear `, ' + 'and includes one activation code in one line') raise CliError('invalid activation file "%s"' % filename) def _register_offline_license(self, fzip, namelist): @@ -817,7 +820,7 @@ def request_ci_regfile(self, regfile): ucode = reginfo['ucode'] rcode = reginfo['rcode'] if len(ucode) != 192: - raise CliError('invalid registration file "%s"', regfile) + raise CliError('invalid ci registration file "%s"', regfile) url = self.regurl('ci/%s' % ucode) paras = ('rev', str(rev)), ('cirev', str(cirev)) From f033f8088da7660feabef502898265531e0944a6 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 23 Jan 2026 09:15:24 +0800 Subject: [PATCH 313/337] Add notes for security --- docs/how-to/security.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/how-to/security.rst b/docs/how-to/security.rst index ebbbf5b0..8dcc6406 100644 --- a/docs/how-to/security.rst +++ b/docs/how-to/security.rst @@ -34,6 +34,12 @@ The following options prevent functions or modules from being replaced by hack c * :option:`--assert-call` * :option:`--assert-import` +.. important:: + + If using :option:`--enable-rft`, please also check :doc:`../topic/rftmode`. The default way is simple to use, but may leave more names not changed. If need rename more names, try to use **rft-auto-include** method. + + If using :option:`--enable-bcc`, please also check :doc:`../topic/rftmode` to make sure most of scripts are converted to C code + What's the best performance pyarmor could do? ============================================= @@ -106,4 +112,6 @@ Pyarmor will clear the module level code after the module is imported, the injec But the main script module level code is never cleared, so moving unnecessary code here to another module could improve security. +Note that for `BCC Mode`, the module level code is not converted to C code, so do not move code to module level if using :option:`--enable-bcc`. + .. include:: ../_common_definitions.txt From ed6f239dd5e9151aa1e18a1734b58b7b9edc97af Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Tue, 27 Jan 2026 17:43:01 +0800 Subject: [PATCH 314/337] Refine bug template --- .github/ISSUE_TEMPLATE/bug_report.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 74544083..fc65f698 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,14 +9,14 @@ assignees: '' **Please remove this line and all the following lines when submit issue** -In order to solve your issue quickly, please read this page and try common solutions in the section `Asking questions in GitHub` -https://pyarmor.readthedocs.io/en/latest/questions.html +In order to solve your issue quickly, please refer to `Ask Pyarmor` in this page +https://pyarmor.eke.org.cn/ -If it's really a bug, please following the guide in the section `Reporting issues` +If it's really a bug, please following the guide in the section `Report issue` -为了快速解决你遇到的问题,请务必打开下面的链接并尝试其中关于常见问题的通用解决方案 -https://pyarmor.readthedocs.io/en/latest/questions.html +为了快速解决你遇到的问题,请参考下面页面中的 `解疑答惑` +https://pyarmor.eke.org.cn/ -如果确认是一个 Bug,建议按照上面链接中的章节 `报告问题` 的规范提交问题报告 +如果确认是一个 Bug,请按照 `报告问题` 的规范提交问题报告 **在提交报告时候请删除当前行以及上面的所有行** From 00fc6f1d27e0f3938ddc4d5f3d365b7520c14b8f Mon Sep 17 00:00:00 2001 From: Adam Huganir Date: Wed, 28 Jan 2026 14:17:41 -0500 Subject: [PATCH 315/337] simple typo in docs --- docs/how-to/register.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/register.rst b/docs/how-to/register.rst index 273379a3..cadaa3f3 100644 --- a/docs/how-to/register.rst +++ b/docs/how-to/register.rst @@ -446,7 +446,7 @@ If run `pyarmor-auth` in Linux VM or WSL, please check group license could work Using multiple Pyarmor Licenses in same device ============================================== -Generally the registration information is sotred in the Pyarmor :term:`Home Path`, the default value is :file:`~/.pyarmor`. It means +Generally the registration information is stored in the Pyarmor :term:`Home Path`, the default value is :file:`~/.pyarmor`. It means - All Python virtual environments share same registration information - It may not work to register other Pyarmor license in same device From 08ebbcfa9e714db0eafbf7b68ee300fe8d510cc3 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 7 Feb 2026 08:51:27 +0800 Subject: [PATCH 316/337] Refine error message --- src/cli/register.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index be67d1a1..72716fec 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -210,7 +210,8 @@ def parse_keyfile(self, filename): logger.error('please make sure the activation file is ' 'pure text file, the content starts with `Dear `, ' - 'and includes one activation code in one line') + 'and includes the activation code' + '(its length is 192 and no line break)') raise CliError('invalid activation file "%s"' % filename) def _register_offline_license(self, fzip, namelist): From 3c2f97a2a8e9df77279c48c3a7919776bf92c827 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 18 Mar 2026 07:44:18 +0800 Subject: [PATCH 317/337] Fix typo (#2313) --- src/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/README.rst b/src/README.rst index 06383ec2..c2f98dae 100644 --- a/src/README.rst +++ b/src/README.rst @@ -6,7 +6,7 @@ Pyarmor is a command line tool used to obfuscate python scripts, bind obfuscated Key Features ------------ -* The obfuscated scritpt is still a normal `.py` script, in most of cases the original python scripts can be replaced with obfuscated scripts seamlessly. +* The obfuscated script is still a normal `.py` script, in most of cases the original python scripts can be replaced with obfuscated scripts seamlessly. * Provide many ways to obfuscate the scripts to balance security and performance * Rename functions/methods/classes/variables/arguments, irreversible obfuscation * Convert part of Python functions to C function, compile to binary by high optimize option, irreversible obfuscation From 2f2e879653eedf75066d9e07cc07ef96c958987b Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 18 Mar 2026 08:41:30 +0800 Subject: [PATCH 318/337] Refine doc --- src/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/README.rst b/src/README.rst index c2f98dae..ab156b5e 100644 --- a/src/README.rst +++ b/src/README.rst @@ -16,7 +16,7 @@ Key Features Support Platforms ----------------- -* Python 3.7~3.13 +* Python 3.7~3.15 * Windows * Many linuxs, include embedded systems * Apple Intel and Apple Silicon From 9e8858bcdf590491c0dd83f7539381d34ff356d5 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 18 Mar 2026 10:02:48 +0800 Subject: [PATCH 319/337] Change version to 9.2.5 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 251ce1f5..53362ca7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.2' # The full version, including alpha/beta/rc tags. -release = '9.2.4' +release = '9.2.5' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 608527fb..9cbce38d 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.2.4' +__VERSION__ = '9.2.5' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 17668944..789648f0 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 2 -patch = 4 +patch = 5 ;; Compatible core version for package: pyarmor.cli.core cli.core = 8.1.0 diff --git a/src/config.py b/src/config.py index 54542896..7b8d566d 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.2.4' +version = '9.2.5' # The corresponding version of pytransform.so core_version = 'r52.6' From 01dac5c323cf81658b3ee50dee1c6589a8cc9903 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 8 Apr 2026 21:52:54 +0800 Subject: [PATCH 320/337] change core version to 8.1.1 --- setup.py | 2 +- src/cli/core/__init__.py | 2 +- src/cli/default.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index c38edc18..dd664e02 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,7 @@ }, install_requires=[ - 'pyarmor.cli.core~=8.1.0' + 'pyarmor.cli.core~=8.1.1' ], entry_points={ diff --git a/src/cli/core/__init__.py b/src/cli/core/__init__.py index 144b2bbc..8622ee2a 100644 --- a/src/cli/core/__init__.py +++ b/src/cli/core/__init__.py @@ -20,7 +20,7 @@ # @Create Date: Thu Jan 12 17:29:25 CST 2023 # -__VERSION__ = '8.1.0' +__VERSION__ = '8.1.1' PLATFORM_NAMES = ( 'windows.x86_64', 'windows.x86', 'cygwin.x86_64', diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 789648f0..fa5c7178 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -6,7 +6,7 @@ minor = 2 patch = 5 ;; Compatible core version for package: pyarmor.cli.core -cli.core = 8.1.0 +cli.core = 8.1.1 ;; Compatible core version for package: pyarmor.mini ;; Which is required to run MINI, VMC, or ECC mode scripts From 0dd5ee2eedea5b36967420d17c23d2fa96920395 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 9 Apr 2026 06:43:06 +0800 Subject: [PATCH 321/337] Test more Python versions in darwin.arm64 --- tests.8/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests.8/Makefile b/tests.8/Makefile index d065ae02..16f25dd2 100644 --- a/tests.8/Makefile +++ b/tests.8/Makefile @@ -22,8 +22,8 @@ ifeq ($(findstring Linux,$(UNAME)),Linux) else ifeq ($(findstring Darwin,$(UNAME)),Darwin) ifeq ($(ARCH),arm64) HOST ?= darwin.arm64 - PYVERS := 3.9 3.10 - PYTHONBIN := /usr/local/bin/pythonXY + PYVERS := 3.9 3.10 3.11 3.12 3.13 3.14 3.15 + PYTHONBIN := /Users/zhaojunde/workspace/v9-test/venv/XY/bin/pythonXY else HOST ?= darwin.x86_64 PYVERS := ${ALL_PYVERS} From 4208fc5b79313fadf46be5bcd1d72d38a5c7e73c Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 9 Apr 2026 06:56:39 +0800 Subject: [PATCH 322/337] Test more Python versions in linux.aarch64 --- tests.8/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests.8/Makefile b/tests.8/Makefile index 16f25dd2..6fb7913b 100644 --- a/tests.8/Makefile +++ b/tests.8/Makefile @@ -12,8 +12,8 @@ ifeq ($(findstring Linux,$(UNAME)),Linux) PYTHONBIN := /usr/local/bin/pythonXY else ifeq ($(ARCH),aarch64) HOST ?= linux.aarch64 - PYVERS := 3.10 - PYTHONBIN := /usr/local/bin/pythonXY + PYVERS := 3.9 3.10 3.11 3.12 3.13 3.14 3.15 + PYTHONBIN := /usr/bin/pythonXY else HOST ?= linux.x86 PYVERS := ${ALL_PYVERS} From d5e0a03d2226e2c47023f486dde544fb313310f9 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 15 Apr 2026 10:13:41 +0800 Subject: [PATCH 323/337] Add CI License to EULA --- LICENSE | 14 ++++++++++++-- LICENSE-ZH | 18 +++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/LICENSE b/LICENSE index e96ef11b..57ccfbeb 100755 --- a/LICENSE +++ b/LICENSE @@ -71,6 +71,9 @@ accessing or in any other way using Pyarmor - referred to as "user" or 1.16 "Licensed Product" means product has been registered to one Pyarmor License. +1.17 "CI License" + means a kind of Pyarmor License + 2. License Grants and Conditions -------------------------------- @@ -97,11 +100,12 @@ but there are some limitations. User purchases Pyarmor License to unlock these limitations except 2.1.5. -Licensor issues 3 kind of Pyarmor License. +Licensor issues 4 kind of Pyarmor License. 2.2.1 Pyarmor Basic, unlock limitations 2.1.1, 2.1.2, 2.1.4 2.2.2 Pyarmor Pro, unlock limitations 2.1.1, 2.1.2, 2.1.3, 2.1.4 2.2.3 Pyarmor Group, unlock limitations 2.1.1, 2.1.2, 2.1.3, 2.1.4 +2.2.4 Pyrmor CI, unlock limitations 2.1.1, 2.1.2, 2.1.3, 2.1.4 Pyarmor Basic and Pyarmor Pro need internet connection to verify, it doesn't work in the device without internet connection. @@ -110,7 +114,13 @@ Pyarmor Pro can not be used in CI/CD pipeline. Pyarmor Group need not internet connection. -Each Pyarmor License only need pay once, not periodically. +Pyarmor CI can only be used in CI/CD pipeline or docker container. + +Pyarmor Basic, Pro and Group only need pay once, not periodically. + +Pyarmor CI will be expired after one year since purchased date, +licensor won't charge user automatically, but send new invoice to user +before the expiration date to get paid. 2.3 Conditions diff --git a/LICENSE-ZH b/LICENSE-ZH index e86b8122..92bc6bb4 100755 --- a/LICENSE-ZH +++ b/LICENSE-ZH @@ -122,7 +122,7 @@ BCC 加密模式 是说,任何一种使用本软件进行保护的产品都有自己唯一的许可证编号,不允许两种不同产 品使用相同的许可证编号。 -本软件提供三种许可证,分别解锁不同的功能 +本软件提供四种许可证,分别解锁不同的功能 2.2.1 基础版许可证 @@ -136,12 +136,20 @@ BCC 加密模式 专家版许可证加密脚本的时候需要在线验证许可证 +专家版许可证不可用于 CI/CD 管线或者 Docker 容器中 + 2.2.3 集团版许可证 集团版许可证解锁限制 2.1 中的 (1) (2) (3) (4) (5) (7). 集团版许可证加密脚本的时候不需要在线验证许可证 +2.2.4 管道版许可证 + +管道版许可证解锁限制 2.1 中的 (1) (2) (3) (4) (5) (7). + +管道版许可证仅用于 CI/CD 管线或者 Docker 容器中 + 不管哪一种许可证,运行加密脚本的时候都无需验证许可证,本软件对于加密脚本的运行没 有任何控制和限制。 @@ -150,8 +158,12 @@ BCC 加密模式 除了购买软件许可的费用之外,没有其它任何费用。获得许可的用户可以使用本软件在许可 的范围之内加密任何脚本并自由发布,不需要在向许可人支付任何费用。 -购买软件许可的费用是一次性收费,可以永久在购买本软件时候的版本中使用,但是许可证 -可能在任何一个升级版本中失效,许可人不承诺许可证可以在今后所有的升级版本中使用。 +除了管线版许可证,购买软件许可的费用是一次性收费,可以永久在购买本软件时候的版本 +中使用,但是许可证可能在任何一个升级版本中失效,许可人不承诺许可证可以在今后所有 +的升级版本中使用。 + +对于管线版许可证,自购买之日起一年内有效,可以使用在有效期之内发布的任何版本。在 +许可证到期之前,许可人会发送邮件通知用户续费。 一旦激活许可证之后,不在支持退款,购买之后没有激活许可证,在三十天之内支持取消许 可证并退款。但是如果购买时间超过三十天,没有激活的许可证也不再支持退款。 From 81c265d0d4a789d97f4a7f40d5ed6d01872a6da4 Mon Sep 17 00:00:00 2001 From: Brody King <46272894+brodyking@users.noreply.github.com> Date: Sun, 26 Apr 2026 01:00:37 -0500 Subject: [PATCH 324/337] Fix grammar --- docs/tutorial/getting-started.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/getting-started.rst b/docs/tutorial/getting-started.rst index fc2683ef..d49ba25e 100644 --- a/docs/tutorial/getting-started.rst +++ b/docs/tutorial/getting-started.rst @@ -73,7 +73,7 @@ There is an extra Python package :file:`pyarmor_runtime_000000`, which is requir Distributing the obfuscated script ---------------------------------- -Only copy :file:`dist/foo.py` to another machine doesn't work, instead copy all the files in the :file:`dist/`. +Only copying :file:`dist/foo.py` to another machine will not work. Instead, copy all the files in the :file:`dist/`. Why? It's clear after checking the content of :file:`dist/foo.py`: @@ -82,7 +82,7 @@ Why? It's clear after checking the content of :file:`dist/foo.py`: from pyarmor_runtime_000000 import __pyarmor__ __pyarmor__(__name__, __file__, ...) -Actually the obfuscated script can be taken as normal Python script with dependent package :mod:`pyarmor_runtime_000000`, use it as it's not obfuscated. +The obfuscated script can be taken as a normal Python script with dependent package :mod:`pyarmor_runtime_000000`, use it as it's not obfuscated. .. important:: From 077d0c450c418840079748de20a0355bab31bd1a Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 7 May 2026 07:59:43 +0800 Subject: [PATCH 325/337] How to handle error: no shipping information --- docs/reference/errors.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/reference/errors.rst b/docs/reference/errors.rst index fe54c6dd..c9ea1fc7 100644 --- a/docs/reference/errors.rst +++ b/docs/reference/errors.rst @@ -95,8 +95,11 @@ Building Errors If not return this, but raises exception, it's firewall problem, please configure it to allow Python interpreter to visit `pyarmor.dashingsoft.com:80` * - invalid activation file - Make sure it's the attachment of activation email which title likes `[Pyarmor] License Activation Code` - Make sure it's not changed by your email server, it should be same as the email content which starts with `Dear ...` and includes the activation code in one line (it's important there should no line break in the activation code). - + Make sure it's not interpolated by your email server, it should be same as the email content which starts with `Dear ...` and includes the activation code in one line (it's important there should no line break in the activation code). + When you save the attacment, make sure its encoding is ascii. + * - HTTP Error 400 - no shipping information + - Generally something tamper with the activation code sent by Pyarmor Team. Maybe the email client, maybe some auto-fix typos tools or anti-virus tools touch the email content. + Try to check the original email from web portal in the web-browser to verify the activation code. Runtime Errors ============== From 21ab9b82a08203608b3be8dd8fd4f92fda8fc1d2 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 7 May 2026 08:08:22 +0800 Subject: [PATCH 326/337] Refine format --- docs/reference/errors.rst | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/reference/errors.rst b/docs/reference/errors.rst index c9ea1fc7..06dc1425 100644 --- a/docs/reference/errors.rst +++ b/docs/reference/errors.rst @@ -70,7 +70,7 @@ Building Errors Please use ``pyarmor-7`` or downgrade pyarmor to 7.7.4 * - This code has been used too many times - - If this code is used in CI/Docker pipeline, please send **order information** by registration email of this code to pyarmor@163.com to unlock it. Do not send this code only, it doesn't make sense. + - It has been locked and can't be used again. * - no registration code found in pyarmor-regcode-xxxx.txt - Download `pyarmor-regcode-xxxx.txt` again, check its content, make sure it's same as email body * - update license token failed @@ -95,10 +95,19 @@ Building Errors If not return this, but raises exception, it's firewall problem, please configure it to allow Python interpreter to visit `pyarmor.dashingsoft.com:80` * - invalid activation file - Make sure it's the attachment of activation email which title likes `[Pyarmor] License Activation Code` - Make sure it's not interpolated by your email server, it should be same as the email content which starts with `Dear ...` and includes the activation code in one line (it's important there should no line break in the activation code). + + Make sure it's not interpolated by email server or email client. + + It should be same as the email content which starts with `Dear ...` + + It should include the activation code in one line (it's important there should no line break in the activation code). + When you save the attacment, make sure its encoding is ascii. * - HTTP Error 400 - no shipping information - - Generally something tamper with the activation code sent by Pyarmor Team. Maybe the email client, maybe some auto-fix typos tools or anti-virus tools touch the email content. + - Generally something tamper with the activation code sent by Pyarmor Team. + + Maybe the email client, maybe some auto-fix typos tools or anti-virus tools touch the email content. + Try to check the original email from web portal in the web-browser to verify the activation code. Runtime Errors From 25914f929acf172ec37fd01afce2a541f772d0d8 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 7 May 2026 08:58:41 +0800 Subject: [PATCH 327/337] Refine doc --- docs/reference/errors.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/errors.rst b/docs/reference/errors.rst index 06dc1425..a3ef4e13 100644 --- a/docs/reference/errors.rst +++ b/docs/reference/errors.rst @@ -98,11 +98,12 @@ Building Errors Make sure it's not interpolated by email server or email client. + Make sure its encoding is utf-8. + It should be same as the email content which starts with `Dear ...` It should include the activation code in one line (it's important there should no line break in the activation code). - When you save the attacment, make sure its encoding is ascii. * - HTTP Error 400 - no shipping information - Generally something tamper with the activation code sent by Pyarmor Team. From db7dca827779176df31119837142ea3c48355dd7 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 16 May 2026 07:23:33 +0800 Subject: [PATCH 328/337] Add dev.info to group device regfile --- src/cli/register.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli/register.py b/src/cli/register.py index 72716fec..4885d38e 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -791,6 +791,7 @@ def request_device_regfile(self, regfile, devid): f.writestr('license.lic', licdata) f.writestr('.pyarmor_capsule.zip', capsule) f.writestr('group.tokens', b'') + f.writestr('dev.info', b'%s,%s' % (devid, machid)) f.writestr('tokens/' + machid, data) logger.info('please copy deivce regfile to offline device and run') From 524d8f2482b2dc6f73137300a03842ba4a7061f5 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Sat, 16 May 2026 07:32:00 +0800 Subject: [PATCH 329/337] Disable upgrade_to_pro function --- src/cli/register.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cli/register.py b/src/cli/register.py index 4885d38e..dd00abad 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -621,6 +621,8 @@ def prepare(self, keyfile, product, upgrade=False): return info, '\n'.join(lines) def upgrade_to_pro(self, keyfile, product): + raise CliError('this feature is not available now') + logger.info('process upgrading file "%s"', keyfile) reginfo = self.parse_keyfile(keyfile) From 71706f95ae1c44820afbc0480056a7d6f0ffdebd Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 20 May 2026 10:57:36 +0800 Subject: [PATCH 330/337] Update status --- docs/ReleasePlan.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/ReleasePlan.md b/docs/ReleasePlan.md index 43a71f90..87287260 100755 --- a/docs/ReleasePlan.md +++ b/docs/ReleasePlan.md @@ -88,21 +88,28 @@ Status: released (2025-10-31) - Support Python 3.14 and 3.15 (a2) -**9.3** +**9.2.2** -Status: developing +Status: released (2025-12-8) -- Full testcases -- Refine RFT mode to improve performance for big scripts (same as MINI mode) - ECC mode, mini BCC mode, only transform part of function body to C code - VMC mode, simple ECC mode, convert function body to VM, not real C code The following features are cancelled because ECC/VMC mode are introduced: -- Convert unsupported node types as far as possible for BCC mode (cancel) -- Expert users could customize C sources generated by BCC mode (cancel) -- BCC mode support cross-platform (may not if too complex) (cancel) -- Obfuscate string constants in extension module "pyarmor_runtime" (cancel) +- Convert unsupported node types as far as possible for BCC mode (cancelld) +- Expert users could customize C sources generated by BCC mode (cancelld) +- BCC mode support cross-platform (may not if too complex) (cancelled) +- Obfuscate string constants in extension module "pyarmor_runtime" (cancelled) + +**9.3** + +Status: developing + +- Full testcases +- Refine RFT mode to improve performance for big scripts (same as MINI mode) +- `pyarmor build` supports option `--pack` to pack project to one executable +- RFT mode supports module name refactor **9.4** From 203b7a1d5ad81541c5901cb47244a890c70ffade Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 20 May 2026 23:10:22 +0800 Subject: [PATCH 331/337] Add nest function testcase for BCC mode --- tests.8/accept_test.py | 7 +++++++ tests.8/samples/nest.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests.8/samples/nest.py diff --git a/tests.8/accept_test.py b/tests.8/accept_test.py index fc087f72..13e183ff 100755 --- a/tests.8/accept_test.py +++ b/tests.8/accept_test.py @@ -387,6 +387,13 @@ def test_thread_with_bcc(self): 'hello bob', ]])) + @only_protest + def test_bcc_with_nest_functions(self): + args = ['g', '--enable-bcc', 'samples/nest.py'] + self.pyarmor_gen(args) + rc, stdout, stderr = self.assert_python_ok('dist/nest.py') + self.assertIn(b'test nest OK', stdout) + if __name__ == '__main__': logging.getLogger().addHandler(logging.NullHandler()) diff --git a/tests.8/samples/nest.py b/tests.8/samples/nest.py new file mode 100644 index 00000000..e9a739df --- /dev/null +++ b/tests.8/samples/nest.py @@ -0,0 +1,17 @@ +def outer(): + msg = 'this is inner2' + + def inner(): + return 'inner-result' + + def inner2(): + return msg + + print(inner2()) + return inner() + + +if __name__ == '__main__': + print('calling outer()...') + print('outer() returned:', outer()) + print('test nest OK') From 1eaf2b37d9aebea068b519e1789379fe961b7af5 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Wed, 20 May 2026 23:30:54 +0800 Subject: [PATCH 332/337] Change version to 9.2.6 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 53362ca7..1e90ee5a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.2' # The full version, including alpha/beta/rc tags. -release = '9.2.5' +release = '9.2.6' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 9cbce38d..6af3b632 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.2.5' +__VERSION__ = '9.2.6' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index fa5c7178..00aa6872 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 2 -patch = 5 +patch = 6 ;; Compatible core version for package: pyarmor.cli.core cli.core = 8.1.1 diff --git a/src/config.py b/src/config.py index 7b8d566d..e1a8a90b 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.2.5' +version = '9.2.6' # The corresponding version of pytransform.so core_version = 'r52.6' From f6fdcb1b2b8cfc9e2d03b008f4b4936b65631659 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 29 May 2026 16:35:36 +0800 Subject: [PATCH 333/337] Fix bug #2339 --- src/cli/register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index dd00abad..7c2fbf1e 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -793,7 +793,7 @@ def request_device_regfile(self, regfile, devid): f.writestr('license.lic', licdata) f.writestr('.pyarmor_capsule.zip', capsule) f.writestr('group.tokens', b'') - f.writestr('dev.info', b'%s,%s' % (devid, machid)) + f.writestr('dev.info', ('%s,%s' % (devid, machid)).encode('utf-8')) f.writestr('tokens/' + machid, data) logger.info('please copy deivce regfile to offline device and run') From a6658ff8415570ace5cd550e0b294f52992ea800 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Mon, 1 Jun 2026 23:06:29 +0800 Subject: [PATCH 334/337] Refine build script to support `--build-number` --- scripts/build-package.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/build-package.sh b/scripts/build-package.sh index af0934d0..ced4e406 100755 --- a/scripts/build-package.sh +++ b/scripts/build-package.sh @@ -2,7 +2,11 @@ # # Build source and wheel distribute # - +# If need new build with same patch version, pass BUILD_NUMBER in command line +# For example, +# +# $ BUILD_NUMBER="--build-number 2" bash scripts/build_package.sh +# PLATFORMS="win32 win_amd64 manylinux1_x86_64 macosx_10_11_x86_64 macosx_10_11_intel" PYTHON=C:/Python34/python @@ -45,11 +49,11 @@ cd ~/workspace/pyarmor make_platform_files # Build source -$PYTHON setup.py sdist --formats=zip,bztar,gztar +$PYTHON setup.py sdist --formats=gztar clear_build # Build universal wheel -$PYTHON setup.py bdist_wheel --python-tag=py3 +$PYTHON setup.py bdist_wheel --python-tag=py3 ${BUILD_NUMBER} clear_build clear_platform_files From a19aea0cc548f88c7ad8b624a10451b65fa40f66 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Thu, 13 Aug 2026 16:23:13 +0800 Subject: [PATCH 335/337] Add note for runner in CI/CD --- docs/how-to/ci.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/how-to/ci.rst b/docs/how-to/ci.rst index 96a7c1d8..5dc4fa45 100644 --- a/docs/how-to/ci.rst +++ b/docs/how-to/ci.rst @@ -9,6 +9,12 @@ There are 2 ways to use Pyarmor in CI/CD pipeline: - Direct way, it's simple, but only works for Trial, Basic and CI license, and there is rate limits - Indirect way, it need change the original build workflow, but works for any type license +.. note:: + + In this page, CI/CD pipeline means the runner is unstable device like docker containter, which machine id is changed after reboot. + + If the runner in CI/CD pipeline is physical machine, it's not in CI/CD pipeline defined by Pyarmor, it can be taken as one normal device. + Direct Way ========== From bce6b724e035b584ebb0a6f5216522eef666e0f6 Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 14 Aug 2026 08:02:02 +0800 Subject: [PATCH 336/337] Fix bug #2368 --- src/cli/register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/register.py b/src/cli/register.py index 7c2fbf1e..628dd49e 100644 --- a/src/cli/register.py +++ b/src/cli/register.py @@ -644,7 +644,7 @@ def upgrade_to_pro(self, keyfile, product): 'use this file for subsequent registration' % regfile, logger.info('Import Notes:\n\n%s\n', notes) - def register(self, keyfile, product, upgrade=False, group=False): + def register(self, keyfile, product=None, upgrade=False, group=False): if keyfile.endswith('.zip'): logger.info('register "%s"', keyfile) self.register_regfile(keyfile) From 93460312b9820d219db42f5aee319650a3b6891c Mon Sep 17 00:00:00 2001 From: Jondy Zhao Date: Fri, 14 Aug 2026 08:18:22 +0800 Subject: [PATCH 337/337] Change version to 9.2.7 --- docs/conf.py | 2 +- src/cli/__init__.py | 2 +- src/cli/default.cfg | 2 +- src/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 1e90ee5a..74ae136b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # The short X.Y version. version = '9.2' # The full version, including alpha/beta/rc tags. -release = '9.2.6' +release = '9.2.7' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/cli/__init__.py b/src/cli/__init__.py index 6af3b632..70ffb8c1 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -1,6 +1,6 @@ import logging -__VERSION__ = '9.2.6' +__VERSION__ = '9.2.7' logger = logging.getLogger('cli') diff --git a/src/cli/default.cfg b/src/cli/default.cfg index 00aa6872..41b772e6 100644 --- a/src/cli/default.cfg +++ b/src/cli/default.cfg @@ -3,7 +3,7 @@ ;; Pyarmor version major = 9 minor = 2 -patch = 6 +patch = 7 ;; Compatible core version for package: pyarmor.cli.core cli.core = 8.1.1 diff --git a/src/config.py b/src/config.py index e1a8a90b..03e63294 100755 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ from sys import platform -version = '9.2.6' +version = '9.2.7' # The corresponding version of pytransform.so core_version = 'r52.6'