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.
Files changed (23) hide show
  1. autocython_zhang-2.3.1/AutoCython/AutoCython.py +44 -0
  2. {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython/_version.py +1 -1
  3. {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython/compile.py +57 -17
  4. autocython_zhang-2.3.1/AutoCython/obfuscate.py +498 -0
  5. {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython/run_tasks.py +19 -21
  6. {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython/tools.py +16 -9
  7. autocython_zhang-2.3.1/AutoCython_zhang.egg-info/PKG-INFO +280 -0
  8. {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython_zhang.egg-info/SOURCES.txt +2 -0
  9. {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython_zhang.egg-info/top_level.txt +1 -0
  10. autocython_zhang-2.3.1/MANIFEST.in +2 -0
  11. autocython_zhang-2.3.1/PKG-INFO +280 -0
  12. autocython_zhang-2.3.1/README.md +260 -0
  13. {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/pyproject.toml +17 -2
  14. autocython_zhang-2.2.1/AutoCython/AutoCython.py +0 -36
  15. autocython_zhang-2.2.1/AutoCython_zhang.egg-info/PKG-INFO +0 -108
  16. autocython_zhang-2.2.1/PKG-INFO +0 -108
  17. autocython_zhang-2.2.1/README.md +0 -67
  18. {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython/__init__.py +0 -0
  19. {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython_zhang.egg-info/dependency_links.txt +0 -0
  20. {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython_zhang.egg-info/entry_points.txt +0 -0
  21. {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/AutoCython_zhang.egg-info/requires.txt +0 -0
  22. {autocython_zhang-2.2.1 → autocython_zhang-2.3.1}/LICENSE +0 -0
  23. {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,4 +2,4 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  """版本号模块 - 单一版本号来源"""
4
4
 
5
- __version__ = "2.2.1"
5
+ __version__ = "2.3.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, file_name)
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': True,
92
+ 'embedsignature': False,
62
93
  'wraparound': False,
63
94
  }}
64
95
 
65
96
  setup(
66
97
  ext_modules=cythonize(
67
- {repr(file_name)},
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
- result = subprocess.run(
80
- command,
81
- cwd=temp_dir,
82
- stdout=subprocess.PIPE,
83
- stderr=subprocess.PIPE
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"{module_name}*{target_ext}")
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"*{module_name}*{target_ext}")
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.getctime)
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)