AutoCython-zhang 2.2.1__tar.gz → 2.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autocython_zhang-2.3.1/AutoCython/AutoCython.py +44 -0
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython/_version.py +1 -1
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython/compile.py +57 -17
- autocython_zhang-2.3.1/AutoCython/obfuscate.py +498 -0
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython/run_tasks.py +19 -21
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython/tools.py +16 -9
- autocython_zhang-2.3.1/AutoCython_zhang.egg-info/PKG-INFO +280 -0
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython_zhang.egg-info/SOURCES.txt +2 -0
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython_zhang.egg-info/top_level.txt +1 -0
- autocython_zhang-2.3.1/MANIFEST.in +2 -0
- autocython_zhang-2.3.1/PKG-INFO +280 -0
- autocython_zhang-2.3.1/README.md +260 -0
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/pyproject.toml +17 -2
- autocython_zhang-2.2.1/AutoCython/AutoCython.py +0 -36
- autocython_zhang-2.2.1/AutoCython_zhang.egg-info/PKG-INFO +0 -108
- autocython_zhang-2.2.1/PKG-INFO +0 -108
- autocython_zhang-2.2.1/README.md +0 -67
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython/__init__.py +0 -0
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython_zhang.egg-info/dependency_links.txt +0 -0
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython_zhang.egg-info/entry_points.txt +0 -0
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython_zhang.egg-info/requires.txt +0 -0
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/LICENSE +0 -0
- {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/setup.cfg +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from .run_tasks import run_tasks
|
|
4
|
+
from .compile import compile_to_binary
|
|
5
|
+
from .tools import parse_arguments, find_python_files
|
|
6
|
+
from .tools import show_no_compilable_files, show_file_not_found, show_path_not_found
|
|
7
|
+
|
|
8
|
+
def compile():
|
|
9
|
+
try:
|
|
10
|
+
args = parse_arguments()
|
|
11
|
+
obfuscate_seed = args.seed
|
|
12
|
+
|
|
13
|
+
if args.file:
|
|
14
|
+
if os.path.isfile(args.file):
|
|
15
|
+
compile_file = args.file
|
|
16
|
+
del_source = args.del_source
|
|
17
|
+
tasks = [
|
|
18
|
+
# 函数, 位置参数, 关键字参数
|
|
19
|
+
(compile_to_binary, compile_file, (compile_file, del_source, True, obfuscate_seed), {}),
|
|
20
|
+
]
|
|
21
|
+
run_tasks(tasks, max_workers=1, raise_on_failure=True) # 执行编译
|
|
22
|
+
else:
|
|
23
|
+
show_file_not_found(args.file)
|
|
24
|
+
elif args.path:
|
|
25
|
+
if os.path.exists(args.path) and not os.path.isfile(args.path):
|
|
26
|
+
compile_file_list = find_python_files(args.path)
|
|
27
|
+
if compile_file_list:
|
|
28
|
+
del_source = args.del_source
|
|
29
|
+
tasks = []
|
|
30
|
+
for compile_file in compile_file_list:
|
|
31
|
+
tasks.append(
|
|
32
|
+
# 函数, 位置参数, 关键字参数
|
|
33
|
+
(compile_to_binary, compile_file, (compile_file, del_source, True, obfuscate_seed), {}),
|
|
34
|
+
)
|
|
35
|
+
run_tasks(tasks, max_workers=args.conc, raise_on_failure=True) # 执行编译
|
|
36
|
+
else:
|
|
37
|
+
show_no_compilable_files(args.path)
|
|
38
|
+
else:
|
|
39
|
+
show_path_not_found(args.path)
|
|
40
|
+
except KeyboardInterrupt:
|
|
41
|
+
sys.exit(130)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
44
|
+
sys.exit(1)
|
|
@@ -2,21 +2,37 @@ import os
|
|
|
2
2
|
import sys
|
|
3
3
|
import glob
|
|
4
4
|
import shutil
|
|
5
|
+
import platform
|
|
5
6
|
import tempfile
|
|
6
7
|
import subprocess
|
|
7
8
|
|
|
9
|
+
from .obfuscate import obfuscate_source
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _strip_binary(path):
|
|
13
|
+
"""strip 符号表,失败静默"""
|
|
14
|
+
try:
|
|
15
|
+
if sys.platform.startswith('win'):
|
|
16
|
+
return
|
|
17
|
+
cmd = ['strip', '-x' if sys.platform == 'darwin' else '--strip-all', path]
|
|
18
|
+
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
19
|
+
except Exception:
|
|
20
|
+
pass
|
|
21
|
+
|
|
8
22
|
def get_platform_extension() -> str:
|
|
9
23
|
"""返回当前平台的扩展名"""
|
|
10
24
|
if sys.platform.startswith('win'):
|
|
11
25
|
return '.pyd'
|
|
12
26
|
return '.so'
|
|
13
27
|
|
|
14
|
-
def compile_to_binary(file_path: str, del_source=False):
|
|
28
|
+
def compile_to_binary(file_path: str, del_source=False, obfuscate=True, obfuscate_seed=None):
|
|
15
29
|
"""
|
|
16
30
|
将指定的 Python 文件(.py)通过 Cython 编译为二进制扩展文件
|
|
17
31
|
|
|
18
32
|
:param file_path: Python 文件路径(可以是相对路径或绝对路径)
|
|
19
33
|
:param del_source: 是否删除源代码
|
|
34
|
+
:param obfuscate: 是否在编译前混淆源码(默认True)
|
|
35
|
+
:param obfuscate_seed: 混淆随机种子(None 表示不固定)
|
|
20
36
|
:return: 生成的二进制文件路径(与输入保持相同的路径类型)
|
|
21
37
|
"""
|
|
22
38
|
# 保存原始路径类型(相对/绝对)
|
|
@@ -36,6 +52,10 @@ def compile_to_binary(file_path: str, del_source=False):
|
|
|
36
52
|
if ext != ".py":
|
|
37
53
|
raise ValueError(f"ValueError: The file {file_path} is not a valid Python file (.py)!")
|
|
38
54
|
|
|
55
|
+
# 连字符文件名转下划线(Cython 模块名不允许连字符)
|
|
56
|
+
safe_module_name = module_name.replace('-', '_')
|
|
57
|
+
safe_file_name = safe_module_name + ext
|
|
58
|
+
|
|
39
59
|
# 获取平台特定扩展名
|
|
40
60
|
target_ext = get_platform_extension()
|
|
41
61
|
|
|
@@ -43,10 +63,21 @@ def compile_to_binary(file_path: str, del_source=False):
|
|
|
43
63
|
temp_dir = tempfile.mkdtemp()
|
|
44
64
|
|
|
45
65
|
try:
|
|
46
|
-
#
|
|
47
|
-
temp_file_path = os.path.join(temp_dir,
|
|
66
|
+
# 将目标文件复制到临时目录(使用安全文件名)
|
|
67
|
+
temp_file_path = os.path.join(temp_dir, safe_file_name)
|
|
48
68
|
shutil.copy2(abs_file_path, temp_file_path)
|
|
49
69
|
|
|
70
|
+
# 混淆源码(失败则显式抛错,避免静默降级)
|
|
71
|
+
if obfuscate:
|
|
72
|
+
try:
|
|
73
|
+
with open(temp_file_path, 'r', encoding='utf-8') as f:
|
|
74
|
+
original_source = f.read()
|
|
75
|
+
obfuscated = obfuscate_source(original_source, seed=obfuscate_seed)
|
|
76
|
+
with open(temp_file_path, 'w', encoding='utf-8') as f:
|
|
77
|
+
f.write(obfuscated)
|
|
78
|
+
except Exception as exc:
|
|
79
|
+
raise RuntimeError(f"Obfuscation failed for {file_path}: {exc}") from exc
|
|
80
|
+
|
|
50
81
|
# 创建临时的 setup.py 文件
|
|
51
82
|
setup_code = f"""
|
|
52
83
|
from setuptools import setup
|
|
@@ -58,13 +89,13 @@ compiler_directives = {{
|
|
|
58
89
|
'annotation_typing': False,
|
|
59
90
|
'always_allow_keywords': True,
|
|
60
91
|
'binding': True,
|
|
61
|
-
'embedsignature':
|
|
92
|
+
'embedsignature': False,
|
|
62
93
|
'wraparound': False,
|
|
63
94
|
}}
|
|
64
95
|
|
|
65
96
|
setup(
|
|
66
97
|
ext_modules=cythonize(
|
|
67
|
-
{repr(
|
|
98
|
+
{repr(safe_file_name)},
|
|
68
99
|
compiler_directives=compiler_directives,
|
|
69
100
|
force=True
|
|
70
101
|
)
|
|
@@ -76,30 +107,34 @@ setup(
|
|
|
76
107
|
|
|
77
108
|
# 执行编译命令
|
|
78
109
|
command = [sys.executable, "setup.py", "build_ext", "--inplace"]
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
110
|
+
try:
|
|
111
|
+
result = subprocess.run(
|
|
112
|
+
command,
|
|
113
|
+
cwd=temp_dir,
|
|
114
|
+
stdout=subprocess.PIPE,
|
|
115
|
+
stderr=subprocess.PIPE,
|
|
116
|
+
timeout=300
|
|
117
|
+
)
|
|
118
|
+
except subprocess.TimeoutExpired:
|
|
119
|
+
raise RuntimeError(f"Compilation timed out after 300s: {file_path}")
|
|
85
120
|
|
|
86
121
|
if result.returncode != 0:
|
|
87
122
|
error_msg = result.stderr.decode('utf-8', errors='replace')
|
|
88
123
|
raise RuntimeError(f"Compilation failed: {error_msg}")
|
|
89
124
|
|
|
90
125
|
# 查找生成的二进制文件
|
|
91
|
-
pattern = os.path.join(temp_dir, f"{
|
|
126
|
+
pattern = os.path.join(temp_dir, f"{safe_module_name}*{target_ext}")
|
|
92
127
|
matches = glob.glob(pattern)
|
|
93
128
|
|
|
94
129
|
if not matches:
|
|
95
|
-
pattern = os.path.join(temp_dir, f"*{
|
|
130
|
+
pattern = os.path.join(temp_dir, f"*{safe_module_name}*{target_ext}")
|
|
96
131
|
matches = glob.glob(pattern)
|
|
97
132
|
|
|
98
133
|
if not matches:
|
|
99
134
|
raise FileNotFoundError(f"FileNotFoundError: The file {file_path} is not a valid Python file (.py)! Generated file {target_ext} not found, in {temp_dir} possible file: {os.listdir(temp_dir)}")
|
|
100
135
|
|
|
101
136
|
# 取最新生成的文件
|
|
102
|
-
generated_file = max(matches, key=os.path.
|
|
137
|
+
generated_file = max(matches, key=os.path.getmtime)
|
|
103
138
|
|
|
104
139
|
# 获取源文件的目录(使用原始路径类型)
|
|
105
140
|
if is_absolute:
|
|
@@ -116,9 +151,14 @@ setup(
|
|
|
116
151
|
output_file_name = os.path.basename(generated_file)
|
|
117
152
|
output_path = os.path.join(output_dir, output_file_name)
|
|
118
153
|
|
|
119
|
-
#
|
|
154
|
+
# 移动文件(先删除已存在的目标)
|
|
155
|
+
if os.path.exists(output_path):
|
|
156
|
+
os.remove(output_path)
|
|
120
157
|
shutil.move(generated_file, output_path)
|
|
121
158
|
|
|
159
|
+
# strip 符号表
|
|
160
|
+
_strip_binary(output_path)
|
|
161
|
+
|
|
122
162
|
if del_source:
|
|
123
163
|
os.remove(file_path)
|
|
124
164
|
|
|
@@ -129,7 +169,7 @@ setup(
|
|
|
129
169
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
130
170
|
|
|
131
171
|
# 测试函数
|
|
132
|
-
if __name__ == "__main__":
|
|
172
|
+
if __name__ == "__main__": # pragma: no cover
|
|
133
173
|
# 替换为你的 Python 文件路径
|
|
134
174
|
target_file = "test/example.py" # 请确保文件路径正确
|
|
135
175
|
|
|
@@ -137,4 +177,4 @@ if __name__ == "__main__":
|
|
|
137
177
|
output_file = compile_to_binary(target_file)
|
|
138
178
|
print(output_file)
|
|
139
179
|
except Exception as e:
|
|
140
|
-
print(e)
|
|
180
|
+
print(e)
|