pycc-compiler 1.4.6__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.
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: pycc-compiler
3
+ Version: 1.4.6
4
+ Summary: Compile Python to ELF executable
5
+ Home-page: https://github.com/yourname/pycc
6
+ Author: pycc contributors
7
+ Author-email:
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: Android
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Environment :: Console
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: cython>=3.0.0
17
+ Dynamic: author
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: requires-dist
23
+ Dynamic: requires-python
24
+ Dynamic: summary
25
+
26
+ # pycc - Python to ELF Compiler
27
+
28
+ A tool to compile Python/Pyrex files to standalone executables or shared libraries.
29
+
30
+ ## Features
31
+
32
+ - Compile `.py` and `.pyx` files to ELF executables
33
+ - Generate shared libraries (`.so`) for Python imports
34
+ - Support `-c` to generate C files, `-cpp` for C++ files
35
+ - Interactive mode for rapid prototyping
36
+ - Optimize with `-O0` to `-O3`
37
+ - Static compilation with clang
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install pycc-compiler
@@ -0,0 +1,17 @@
1
+ # pycc - Python to ELF Compiler
2
+
3
+ A tool to compile Python/Pyrex files to standalone executables or shared libraries.
4
+
5
+ ## Features
6
+
7
+ - Compile `.py` and `.pyx` files to ELF executables
8
+ - Generate shared libraries (`.so`) for Python imports
9
+ - Support `-c` to generate C files, `-cpp` for C++ files
10
+ - Interactive mode for rapid prototyping
11
+ - Optimize with `-O0` to `-O3`
12
+ - Static compilation with clang
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install pycc-compiler
@@ -0,0 +1,14 @@
1
+ #!/data/data/com.termux/files/usr/bin/python
2
+ """
3
+ pycc - Python to ELF Compiler
4
+ Compile Python/Pyrex files to standalone executables or shared libraries.
5
+ """
6
+
7
+ __version__ = "1.4.6"
8
+ __author__ = "pycc contributors"
9
+ __description__ = "Compile Python to ELF executable"
10
+
11
+ from .pycc import main
12
+
13
+ if __name__ == "__main__":
14
+ main()
@@ -0,0 +1,878 @@
1
+ #!/data/data/com.termux/files/usr/bin/python
2
+
3
+ import os
4
+ import sys
5
+ import shutil
6
+ import subprocess
7
+ import re
8
+ import readline
9
+ import time
10
+
11
+ VERSION = "1.4.6"
12
+ DESCRIPTION = "compile python to ELF"
13
+
14
+ # 颜色代码
15
+ RED = '\033[91m'
16
+ GREEN = '\033[92m'
17
+ YELLOW = '\033[93m'
18
+ BLUE = '\033[94m'
19
+ CYAN = '\033[96m'
20
+ NC = '\033[0m'
21
+
22
+ VERBOSE = False
23
+ QUIET = False
24
+
25
+ def print_error(msg):
26
+ print(f"pycc: {RED}error:{NC} {msg}", file=sys.stderr)
27
+
28
+ def print_warning(msg):
29
+ if not QUIET:
30
+ print(f"pycc: {YELLOW}warning:{NC} {msg}", file=sys.stderr)
31
+
32
+ def print_success(msg):
33
+ if VERBOSE and not QUIET:
34
+ print(f"pycc: {GREEN}success:{NC} {msg}", file=sys.stderr)
35
+
36
+ def print_info(msg):
37
+ if VERBOSE and not QUIET:
38
+ print(f"pycc: {msg}", file=sys.stderr)
39
+
40
+ def print_debug(msg):
41
+ if VERBOSE:
42
+ print(msg, file=sys.stderr)
43
+
44
+ def print_cmd(cmd):
45
+ if VERBOSE:
46
+ print(' '.join(cmd), file=sys.stderr)
47
+
48
+ def spinning_cursor():
49
+ while True:
50
+ for cursor in '|/-\\':
51
+ yield cursor
52
+
53
+ def show_progress(message, duration=0.5):
54
+ spinner = spinning_cursor()
55
+ end_time = time.time() + duration
56
+ while time.time() < end_time:
57
+ sys.stderr.write(f'\r{message} {next(spinner)} ')
58
+ sys.stderr.flush()
59
+ time.sleep(0.1)
60
+ sys.stderr.write('\r' + ' ' * (len(message) + 2) + '\r')
61
+ sys.stderr.flush()
62
+
63
+ def get_python_version():
64
+ env_version = os.environ.get('PYCC_PY_VERSION')
65
+ if env_version:
66
+ print_debug(f"Using PYCC_PY_VERSION={env_version}")
67
+ return env_version
68
+
69
+ try:
70
+ result = subprocess.run(
71
+ ['python3', '-c', 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")'],
72
+ capture_output=True, text=True
73
+ )
74
+ if result.returncode == 0:
75
+ version = result.stdout.strip()
76
+ print_debug(f"Detected Python version: {version}")
77
+ return version
78
+ except:
79
+ pass
80
+
81
+ print_debug("Using default Python version: 3.13")
82
+ return "3.13"
83
+
84
+ def get_clang_version():
85
+ try:
86
+ result = subprocess.run(['clang', '--version'], capture_output=True, text=True)
87
+ if result.returncode == 0:
88
+ lines = result.stdout.strip().split('\n')
89
+ if lines:
90
+ return lines[0]
91
+ except:
92
+ pass
93
+ return "clang"
94
+
95
+ def get_python_include():
96
+ py_version = get_python_version()
97
+ include_path = f"/data/data/com.termux/files/usr/include/python{py_version}"
98
+
99
+ if os.path.exists(include_path):
100
+ print_debug(f"Using Python include: {include_path}")
101
+ return include_path
102
+
103
+ try:
104
+ result = subprocess.run(['python3-config', '--includes'], capture_output=True, text=True)
105
+ if result.returncode == 0:
106
+ match = re.search(r'-I([^\s]+)', result.stdout)
107
+ if match:
108
+ path = match.group(1)
109
+ print_debug(f"Got include from python3-config: {path}")
110
+ return path
111
+ except:
112
+ pass
113
+
114
+ print_debug(f"Using default include: {include_path}")
115
+ return include_path
116
+
117
+ def get_python_lib():
118
+ py_version = get_python_version()
119
+ lib_path = f"/data/data/com.termux/files/usr/lib"
120
+ lib_name = f"python{py_version}"
121
+
122
+ print_debug(f"Python lib path: {lib_path}, lib name: {lib_name}")
123
+ return lib_path, lib_name
124
+
125
+ def check_dynamic_code(content):
126
+ patterns = [r'compile\s*\(', r'exec\s*\(', r'eval\s*\(']
127
+ for pattern in patterns:
128
+ if re.search(pattern, content):
129
+ return True
130
+ return False
131
+
132
+ def run_with_python(input_file, args):
133
+ cmd = ['python', input_file] + args
134
+ print_debug(f"Running Python: {' '.join(cmd)}")
135
+ os.system(' '.join(cmd))
136
+
137
+ def get_compiler(input_file):
138
+ if input_file.endswith(('.c')):
139
+ return 'clang'
140
+ elif input_file.endswith(('.cpp', '.cc', '.cxx')):
141
+ return 'clang++'
142
+ else:
143
+ return None
144
+
145
+ def is_valid_module_name(name):
146
+ return re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', name) is not None
147
+
148
+ def fix_module_name(input_file):
149
+ base_name = os.path.splitext(os.path.basename(input_file))[0]
150
+
151
+ if is_valid_module_name(base_name):
152
+ print_debug(f"Valid module name: {base_name}")
153
+ return input_file, None
154
+
155
+ new_base_name = f"pycc_v{VERSION}_{base_name}"
156
+ new_base_name = re.sub(r'[^a-zA-Z0-9_]', '_', new_base_name)
157
+ if not re.match(r'^[a-zA-Z_]', new_base_name):
158
+ new_base_name = '_' + new_base_name
159
+
160
+ new_file = os.path.join(os.path.dirname(input_file), f"{new_base_name}.py")
161
+
162
+ print_warning(f"Invalid module name '{base_name}', renamed to '{new_base_name}'")
163
+ print_debug(f"Renamed: {input_file} -> {new_file}")
164
+
165
+ shutil.copy2(input_file, new_file)
166
+
167
+ return new_file, new_base_name
168
+
169
+ def run_clang_mode(args):
170
+ if len(args) < 2:
171
+ print_error("--clang mode missing arguments")
172
+ sys.exit(1)
173
+
174
+ clang_args = args[1:]
175
+ compiler = 'clang'
176
+ output_file = None
177
+
178
+ for i, arg in enumerate(clang_args):
179
+ if arg in ['-o', '--output'] and i + 1 < len(clang_args):
180
+ output_file = clang_args[i + 1]
181
+ break
182
+
183
+ for arg in clang_args:
184
+ if not arg.startswith('-') and os.path.exists(arg):
185
+ ext_compiler = get_compiler(arg)
186
+ if ext_compiler:
187
+ compiler = ext_compiler
188
+ break
189
+
190
+ if not output_file:
191
+ for arg in clang_args:
192
+ if not arg.startswith('-') and os.path.exists(arg):
193
+ base_name = os.path.splitext(os.path.basename(arg))[0]
194
+ output_file = f"./{base_name}.out"
195
+ clang_args = clang_args[:] + ['-o', output_file]
196
+ break
197
+
198
+ cmd = [compiler] + clang_args
199
+ print_cmd(cmd)
200
+ result = subprocess.run(cmd)
201
+ sys.exit(result.returncode)
202
+
203
+ def add_compile_marker(output_file):
204
+ marker = f"Compiled by pycc {VERSION}"
205
+ try:
206
+ with open(output_file, 'ab') as f:
207
+ f.write(f"\n{marker}\0".encode())
208
+ print_debug(f"Added compile marker: {marker}")
209
+ except Exception as e:
210
+ print_debug(f"Failed to add marker: {e}")
211
+
212
+ def compile_to_so(input_file, output, opt_level, other_flags, site_install, py_version, py_include, py_lib_path, py_lib_name):
213
+ file_ext = os.path.splitext(input_file)[1].lower()
214
+ is_c = file_ext in ['.c', '.cpp', '.cc', '.cxx']
215
+
216
+ if is_c:
217
+ if output:
218
+ so_output = output
219
+ else:
220
+ base_name = os.path.splitext(os.path.basename(input_file))[0]
221
+ so_output = f"{base_name}.so"
222
+
223
+ if file_ext == '.c':
224
+ compiler = 'clang'
225
+ else:
226
+ compiler = 'clang++'
227
+
228
+ compile_cmd = [
229
+ compiler, '-shared', '-fPIC', f'-O{opt_level}', '-o', so_output, input_file,
230
+ f'-I{py_include}', f'-L{py_lib_path}', f'-l{py_lib_name}',
231
+ '-lpthread', '-lm', '-lutil', '-ldl'
232
+ ] + other_flags
233
+
234
+ print_cmd(compile_cmd)
235
+ result = subprocess.run(compile_cmd, capture_output=True, text=True)
236
+ if result.returncode != 0:
237
+ print_error("Compilation error:")
238
+ print(result.stderr, file=sys.stderr)
239
+ sys.exit(1)
240
+
241
+ os.chmod(so_output, 0o755)
242
+ add_compile_marker(so_output)
243
+
244
+ if site_install:
245
+ site_dir = f"{py_lib_path}/python{py_version}/site-packages"
246
+ target = os.path.join(site_dir, os.path.basename(so_output))
247
+ shutil.copy2(so_output, target)
248
+ os.chmod(target, 0o755)
249
+ add_compile_marker(target)
250
+ if VERBOSE and not QUIET:
251
+ print_success(f"Installed to: {target}")
252
+ print_info(f"Python {py_version}")
253
+ else:
254
+ if VERBOSE and not QUIET:
255
+ print_info(f"Shared library: {so_output}")
256
+ return
257
+
258
+ work_dir = os.path.expanduser('~/tmp/pycc_build')
259
+ os.makedirs(work_dir, exist_ok=True)
260
+
261
+ try:
262
+ base_name = os.path.splitext(os.path.basename(input_file))[0]
263
+
264
+ if output:
265
+ module_name = os.path.splitext(os.path.basename(output))[0]
266
+ else:
267
+ module_name = base_name
268
+
269
+ if not is_valid_module_name(module_name):
270
+ print_error(f"Invalid module name '{module_name}'")
271
+ sys.exit(1)
272
+
273
+ module_pyx = os.path.join(work_dir, f"{module_name}.pyx")
274
+ shutil.copy2(input_file, module_pyx)
275
+ print_debug(f"Copied: {input_file} -> {module_pyx}")
276
+
277
+ out_file = os.path.join(work_dir, f"{module_name}.c")
278
+ cython_cmd = ['cython', '--embed', '-3', module_pyx, '-o', out_file]
279
+
280
+ print_debug(f"Cython output: {out_file}")
281
+ print_cmd(cython_cmd)
282
+
283
+ result = subprocess.run(cython_cmd, capture_output=True, text=True)
284
+ if result.returncode != 0:
285
+ print_error("Cython error:")
286
+ print(result.stderr, file=sys.stderr)
287
+ sys.exit(1)
288
+
289
+ print_debug("Cython successful")
290
+
291
+ so_output = output if output else f"{module_name}.so"
292
+ compile_cmd = [
293
+ 'clang', '-shared', '-fPIC', f'-O{opt_level}', '-o', so_output, out_file,
294
+ f'-I{py_include}', f'-L{py_lib_path}', f'-l{py_lib_name}',
295
+ '-lpthread', '-lm', '-lutil', '-ldl'
296
+ ] + other_flags
297
+
298
+ print_cmd(compile_cmd)
299
+ result = subprocess.run(compile_cmd, capture_output=True, text=True)
300
+ if result.returncode != 0:
301
+ print_error("Compilation error:")
302
+ print(result.stderr, file=sys.stderr)
303
+ sys.exit(1)
304
+
305
+ os.chmod(so_output, 0o755)
306
+ add_compile_marker(so_output)
307
+
308
+ if site_install:
309
+ site_dir = f"{py_lib_path}/python{py_version}/site-packages"
310
+ target = os.path.join(site_dir, os.path.basename(so_output))
311
+ shutil.copy2(so_output, target)
312
+ os.chmod(target, 0o755)
313
+ add_compile_marker(target)
314
+ if VERBOSE and not QUIET:
315
+ print_success(f"Installed to: {target}")
316
+ print_info(f"Python {py_version}")
317
+ else:
318
+ if VERBOSE and not QUIET:
319
+ print_info(f"Shared library: {so_output}")
320
+
321
+ finally:
322
+ if os.path.exists(work_dir):
323
+ shutil.rmtree(work_dir)
324
+ print_debug(f"Cleaned: {work_dir}")
325
+
326
+ # 交互模式的代码存储
327
+ interactive_code = []
328
+
329
+ def is_print_statement(line):
330
+ """判断是否是 print 语句"""
331
+ stripped = line.strip()
332
+ return re.match(r'^print\s*\(', stripped) is not None
333
+
334
+ def is_shell_command(line):
335
+ """判断是否是 shell 命令(以 shell: 开头)"""
336
+ stripped = line.strip()
337
+ return stripped.startswith('shell:')
338
+
339
+ def is_os_system_output(line):
340
+ """判断是否是 os.system('echo ...') 或 os.system('printf ...') 等输出命令"""
341
+ stripped = line.strip()
342
+ # 匹配 os.system('echo ...') 或 os.system("printf ...")
343
+ pattern = r'^os\.system\s*\(\s*[\'"]?(echo|printf)\s*'
344
+ return re.search(pattern, stripped) is not None
345
+
346
+ def interactive_mode():
347
+ global interactive_code
348
+
349
+ clang_ver = get_clang_version()
350
+ py_ver = get_python_version()
351
+
352
+ print(f"{GREEN}pycc version {VERSION}{NC} [{clang_ver}]")
353
+ print(f"Python {py_ver}")
354
+ print(f'Enter Python commands, type {CYAN}RUN{NC} to compile and execute, {CYAN}exit{NC} to quit')
355
+ print(f' {CYAN}shell:{NC} command - execute shell command (not saved)')
356
+ print(f' Print statements and os.system("echo/printf") are executed but NOT saved')
357
+
358
+ history_file = os.path.expanduser('~/.pycc_history')
359
+ try:
360
+ readline.read_history_file(history_file)
361
+ except:
362
+ pass
363
+
364
+ # 临时文件路径
365
+ temp_py = os.path.expanduser('~/tmp/__pycc_interaction__.py')
366
+ temp_bin = os.path.expanduser('~/tmp/__pycc_interaction__.out')
367
+ os.makedirs(os.path.dirname(temp_py), exist_ok=True)
368
+
369
+ try:
370
+ while True:
371
+ try:
372
+ line = input(f"{BLUE}P>>{NC} ").strip()
373
+ if not line:
374
+ continue
375
+
376
+ if line.lower() == 'exit':
377
+ break
378
+
379
+ if line.upper() == 'RUN':
380
+ if interactive_code:
381
+ with open(temp_py, 'w') as f:
382
+ f.write('\n'.join(interactive_code))
383
+
384
+ show_progress("Compiling", 0.8)
385
+
386
+ pycc_cmd = sys.argv[0]
387
+ if not os.path.exists(pycc_cmd):
388
+ pycc_cmd = shutil.which('pycc')
389
+ if not pycc_cmd:
390
+ pycc_cmd = sys.executable + ' ' + __file__
391
+
392
+ cmd = [pycc_cmd, temp_py, '-o', temp_bin, '-q']
393
+ result = subprocess.run(cmd, capture_output=True, text=True)
394
+
395
+ if result.returncode != 0:
396
+ print(f"{RED}Compilation failed:{NC}")
397
+ if result.stderr:
398
+ for err_line in result.stderr.split('\n'):
399
+ if err_line.strip():
400
+ clean_line = re.sub(r'\x1b\[[0-9;]*m', '', err_line)
401
+ if clean_line.strip():
402
+ print(f" {clean_line}")
403
+ continue
404
+
405
+ os.system('clear')
406
+ print(f"{CYAN}Running...{NC}\n")
407
+ subprocess.run([temp_bin])
408
+ print(f"\n{CYAN}--- Done ---{NC}")
409
+ else:
410
+ print(f"{YELLOW}No code to execute{NC}")
411
+ continue
412
+
413
+ # shell: 命令 - 执行 shell 命令,不保存
414
+ if is_shell_command(line):
415
+ shell_cmd = line[6:].strip() # 去掉 "shell:"
416
+ if shell_cmd:
417
+ print(f"{CYAN}$ {shell_cmd}{NC}")
418
+ os.system(shell_cmd)
419
+ continue
420
+
421
+ # os.system('echo...') 或 os.system('printf...') - 执行但不保存
422
+ if is_os_system_output(line):
423
+ # 执行但不保存
424
+ temp_os_py = os.path.expanduser('~/tmp/__pycc_os__.py')
425
+ with open(temp_os_py, 'w') as f:
426
+ # 先写入所有保存的代码(确保变量存在)
427
+ f.write('\n'.join(interactive_code))
428
+ f.write('\n')
429
+ f.write(line)
430
+
431
+ subprocess.run(['python3', temp_os_py])
432
+ os.unlink(temp_os_py)
433
+ continue
434
+
435
+ # print 语句 - 执行但不保存
436
+ if is_print_statement(line):
437
+ temp_print_py = os.path.expanduser('~/tmp/__pycc_print__.py')
438
+ with open(temp_print_py, 'w') as f:
439
+ f.write('\n'.join(interactive_code))
440
+ f.write('\n')
441
+ f.write(line)
442
+
443
+ subprocess.run(['python3', temp_print_py])
444
+ os.unlink(temp_print_py)
445
+ continue
446
+
447
+ # 其他代码:保存
448
+ interactive_code.append(line)
449
+ try:
450
+ readline.write_history_file(history_file)
451
+ except:
452
+ pass
453
+
454
+ except KeyboardInterrupt:
455
+ print(f"\n{YELLOW}Use 'exit' to quit{NC}")
456
+ except EOFError:
457
+ break
458
+ finally:
459
+ if os.path.exists(temp_py):
460
+ os.unlink(temp_py)
461
+ if os.path.exists(temp_bin):
462
+ os.unlink(temp_bin)
463
+
464
+ if interactive_code:
465
+ print(f"{YELLOW}Unsaved code left (use RUN to execute){NC}")
466
+ try:
467
+ readline.write_history_file(history_file)
468
+ except:
469
+ pass
470
+
471
+ def main():
472
+ global VERBOSE, QUIET
473
+
474
+ if len(sys.argv) > 1 and sys.argv[1] in ['-i', '--interactive']:
475
+ interactive_mode()
476
+ return
477
+
478
+ if len(sys.argv) > 1 and sys.argv[1] in ['-c', '-cpp'] and any(x in sys.argv[2:] for x in ['--clang', '-clg']):
479
+ pycc_source = sys.argv[1]
480
+ output_file = None
481
+ clang_args = []
482
+ i = 1
483
+ while i < len(sys.argv):
484
+ arg = sys.argv[i]
485
+ if arg in ['-c', '-cpp']:
486
+ pycc_source = arg
487
+ i += 1
488
+ elif arg in ['--clang', '-clg']:
489
+ clang_args = sys.argv[i+1:]
490
+ break
491
+ elif arg in ['-o', '--output'] and i+1 < len(sys.argv):
492
+ output_file = sys.argv[i+1]
493
+ i += 2
494
+ else:
495
+ i += 1
496
+
497
+ source_file = None
498
+ for arg in clang_args:
499
+ if not arg.startswith('-') and os.path.exists(arg):
500
+ source_file = arg
501
+ break
502
+
503
+ if not source_file:
504
+ print_error("Missing input file for clang mode")
505
+ sys.exit(1)
506
+
507
+ work_dir = os.path.expanduser('~/tmp/pycc_build')
508
+ os.makedirs(work_dir, exist_ok=True)
509
+
510
+ try:
511
+ base_name = os.path.splitext(os.path.basename(source_file))[0]
512
+ pyx_file = os.path.join(work_dir, f"{base_name}.pyx")
513
+ shutil.copy2(source_file, pyx_file)
514
+
515
+ if pycc_source == '-c':
516
+ out_file = os.path.join(work_dir, f"{base_name}.c")
517
+ cython_cmd = ['cython', '--embed', '-3', pyx_file, '-o', out_file]
518
+ else:
519
+ out_file = os.path.join(work_dir, f"{base_name}.cpp")
520
+ cython_cmd = ['cython', '--embed', '-3', '--cplus', pyx_file, '-o', out_file]
521
+
522
+ print_debug(f"Generating {out_file}")
523
+ result = subprocess.run(cython_cmd, capture_output=True, text=True)
524
+ if result.returncode != 0:
525
+ print_error("Cython error:")
526
+ print(result.stderr, file=sys.stderr)
527
+ sys.exit(1)
528
+
529
+ final_output = output_file if output_file else f"{base_name}.{ 'c' if pycc_source == '-c' else 'cpp' }"
530
+ shutil.copy2(out_file, final_output)
531
+ print_info(f"Generated: {final_output}")
532
+
533
+ if clang_args:
534
+ clang_cmd = ['clang' if final_output.endswith('.c') else 'clang++'] + clang_args
535
+ if not output_file:
536
+ base_out = os.path.splitext(os.path.basename(source_file))[0]
537
+ clang_cmd.extend(['-o', f"{base_out}.out"])
538
+ print_cmd(clang_cmd)
539
+ result = subprocess.run(clang_cmd)
540
+ sys.exit(result.returncode)
541
+ finally:
542
+ if os.path.exists(work_dir):
543
+ shutil.rmtree(work_dir)
544
+ return
545
+
546
+ if len(sys.argv) > 1 and sys.argv[1] in ['--clang', '-clg']:
547
+ run_clang_mode(sys.argv[1:])
548
+ return
549
+
550
+ if '--version' in sys.argv:
551
+ print(f"pycc {VERSION}")
552
+ print(DESCRIPTION)
553
+ return
554
+
555
+ if '--help' in sys.argv or '-h' in sys.argv:
556
+ print(f"pycc {VERSION} - {DESCRIPTION}")
557
+ print("\nUsage: pycc [options] <input.py/.pyx/.c/.cpp> [args...]")
558
+ print(" pycc --clang [clang options...]")
559
+ print(" pycc -i --interactive")
560
+ print("\nOptions:")
561
+ print(" -O0, -O1, -O2, -O3 Optimization level (default -O0)")
562
+ print(" -o, --output Output file name")
563
+ print(" -run Compile and run (no output file kept)")
564
+ print(" -c Generate C file (.c)")
565
+ print(" -cpp Generate C++ file (.cpp)")
566
+ print(" -so Generate shared library (.so)")
567
+ print(" --site, -s Install to site-packages (with -so)")
568
+ print(" -i, --interactive Interactive mode")
569
+ print(" -v Verbose mode (show commands)")
570
+ print(" -q Quiet mode (suppress warnings)")
571
+ print(" -### Show compile commands without executing")
572
+ print(" --version Show version information")
573
+ print(" --help, -h Show this help message")
574
+ print("\nEnvironment:")
575
+ print(" PYCC_PY_VERSION Specify Python version (auto-detected)")
576
+ print("\nClang passthrough mode:")
577
+ print(" --clang, -clg Forward all args to clang/clang++")
578
+ return
579
+
580
+ if len(sys.argv) < 2:
581
+ print_error("Missing input file")
582
+ sys.exit(1)
583
+
584
+ input_file = None
585
+ other_args = []
586
+ script_args = []
587
+
588
+ args_iter = iter(sys.argv[1:])
589
+ for arg in args_iter:
590
+ if not arg.startswith('-') and input_file is None:
591
+ input_file = arg
592
+ elif arg.startswith('-'):
593
+ if arg == '-v':
594
+ VERBOSE = True
595
+ elif arg == '-q':
596
+ QUIET = True
597
+ else:
598
+ other_args.append(arg)
599
+ if arg in ['-o', '--output']:
600
+ try:
601
+ next_arg = next(args_iter)
602
+ other_args.append(next_arg)
603
+ except StopIteration:
604
+ pass
605
+ else:
606
+ if input_file is not None:
607
+ script_args.append(arg)
608
+ else:
609
+ other_args.append(arg)
610
+
611
+ print_debug(f"Input file: {input_file}")
612
+ print_debug(f"Other args: {other_args}")
613
+ print_debug(f"Script args: {script_args}")
614
+
615
+ if not input_file:
616
+ print_error("Missing input file")
617
+ sys.exit(1)
618
+
619
+ if not os.path.exists(input_file):
620
+ print_error(f"File not found: {input_file}")
621
+ sys.exit(1)
622
+
623
+ file_ext = os.path.splitext(input_file)[1].lower()
624
+ is_python = file_ext == '.py'
625
+ is_pyx = file_ext == '.pyx'
626
+ is_c = file_ext in ['.c', '.cpp', '.cc', '.cxx']
627
+
628
+ output_c = '-c' in other_args
629
+ output_cpp = '-cpp' in other_args
630
+ output_so = '-so' in other_args
631
+
632
+ run = '-run' in other_args
633
+ dry_run = '-###' in other_args
634
+
635
+ if dry_run and (output_c or output_cpp or output_so):
636
+ print_error("-### cannot be used with -c/-cpp/-so")
637
+ sys.exit(1)
638
+
639
+ site_install = '--site' in other_args or '-s' in other_args
640
+
641
+ if sum([output_c, output_cpp, output_so]) > 1:
642
+ print_error("Only one of -c, -cpp, -so can be selected")
643
+ sys.exit(1)
644
+
645
+ if is_c and not (output_c or output_cpp or output_so):
646
+ if output_c or output_cpp:
647
+ pass
648
+ else:
649
+ clang_args = [sys.argv[0], '--clang'] + sys.argv[1:]
650
+ has_output = False
651
+ for arg in sys.argv[1:]:
652
+ if arg in ['-o', '--output']:
653
+ has_output = True
654
+ break
655
+
656
+ if not has_output:
657
+ base_name = os.path.splitext(os.path.basename(input_file))[0]
658
+ new_args = []
659
+ for arg in sys.argv[1:]:
660
+ if arg == input_file and not has_output:
661
+ new_args.extend(['-o', f"{base_name}.out", arg])
662
+ has_output = True
663
+ else:
664
+ new_args.append(arg)
665
+ clang_args = [sys.argv[0], '--clang'] + new_args
666
+
667
+ run_clang_mode(clang_args)
668
+ return
669
+
670
+ if is_c and output_so:
671
+ opt_level = "0"
672
+ for opt in ['-O3', '-O2', '-O1', '-O0']:
673
+ if opt in other_args:
674
+ opt_level = opt[2]
675
+ other_args = [arg for arg in other_args if arg != opt]
676
+ break
677
+
678
+ for flag in ['-so', '--site', '-s']:
679
+ if flag in other_args:
680
+ other_args = [arg for arg in other_args if arg != flag]
681
+
682
+ other_flags = [arg for arg in other_args if arg.startswith('-') and arg not in ['-o', '--output']]
683
+
684
+ output = None
685
+ for i, arg in enumerate(other_args):
686
+ if arg in ['-o', '--output'] and i + 1 < len(other_args):
687
+ output = other_args[i + 1]
688
+ break
689
+
690
+ py_version = get_python_version()
691
+ py_include = get_python_include()
692
+ py_lib_path, py_lib_name = get_python_lib()
693
+
694
+ compile_to_so(input_file, output, opt_level, other_flags, site_install,
695
+ py_version, py_include, py_lib_path, py_lib_name)
696
+ return
697
+
698
+ if not (is_python or is_pyx):
699
+ print_error(f"Unsupported file type: {input_file} (supported: .py, .pyx, .c, .cpp)")
700
+ sys.exit(1)
701
+
702
+ opt_level = "0"
703
+ for opt in ['-O3', '-O2', '-O1', '-O0']:
704
+ if opt in other_args:
705
+ opt_level = opt[2]
706
+ other_args = [arg for arg in other_args if arg != opt]
707
+ break
708
+
709
+ print_debug(f"Optimization level: -O{opt_level}")
710
+
711
+ if run:
712
+ other_args = [arg for arg in other_args if arg != '-run']
713
+ print_debug("Mode: -run")
714
+
715
+ if dry_run:
716
+ other_args = [arg for arg in other_args if arg != '-###']
717
+ print_debug("Mode: -###")
718
+
719
+ for flag in ['-c', '-cpp', '-so', '--site', '-s']:
720
+ if flag in other_args:
721
+ other_args = [arg for arg in other_args if arg != flag]
722
+
723
+ other_flags = [arg for arg in other_args if arg.startswith('-') and arg not in ['-o', '--output']]
724
+
725
+ output = None
726
+ for i, arg in enumerate(other_args):
727
+ if arg in ['-o', '--output'] and i + 1 < len(other_args):
728
+ output = other_args[i + 1]
729
+ break
730
+
731
+ print_debug(f"Output file: {output}")
732
+
733
+ if output_so:
734
+ py_version = get_python_version()
735
+ py_include = get_python_include()
736
+ py_lib_path, py_lib_name = get_python_lib()
737
+
738
+ compile_to_so(input_file, output, opt_level, other_flags, site_install,
739
+ py_version, py_include, py_lib_path, py_lib_name)
740
+ return
741
+
742
+ if output_c or output_cpp:
743
+ if is_python:
744
+ original_input = input_file
745
+ input_file, new_base_name = fix_module_name(input_file)
746
+ should_cleanup = new_base_name is not None
747
+ else:
748
+ original_input = input_file
749
+ should_cleanup = False
750
+
751
+ work_dir = os.path.expanduser('~/tmp/pycc_build')
752
+ os.makedirs(work_dir, exist_ok=True)
753
+
754
+ try:
755
+ base_name = os.path.splitext(os.path.basename(input_file))[0]
756
+ pyx_file = os.path.join(work_dir, f"{base_name}.pyx")
757
+ shutil.copy2(input_file, pyx_file)
758
+ print_debug(f"Copied: {input_file} -> {pyx_file}")
759
+
760
+ if output_c:
761
+ out_file = os.path.join(work_dir, f"{base_name}.c")
762
+ cython_cmd = ['cython', '--embed', '-3', pyx_file, '-o', out_file]
763
+ else:
764
+ out_file = os.path.join(work_dir, f"{base_name}.cpp")
765
+ cython_cmd = ['cython', '--embed', '-3', '--cplus', pyx_file, '-o', out_file]
766
+
767
+ print_cmd(cython_cmd)
768
+
769
+ if dry_run:
770
+ print(' '.join(cython_cmd))
771
+ print(f"# Generate: {output if output else out_file}")
772
+ return
773
+
774
+ result = subprocess.run(cython_cmd, capture_output=True, text=True)
775
+ if result.returncode != 0:
776
+ print_error("Cython error:")
777
+ print(result.stderr, file=sys.stderr)
778
+ sys.exit(1)
779
+
780
+ final_output = output if output else out_file
781
+ shutil.copy2(out_file, final_output)
782
+ if VERBOSE and not QUIET:
783
+ print_info(f"Generated: {final_output}")
784
+
785
+ finally:
786
+ if os.path.exists(work_dir) and not dry_run:
787
+ shutil.rmtree(work_dir)
788
+ if should_cleanup and os.path.exists(input_file):
789
+ os.remove(input_file)
790
+ return
791
+
792
+ if is_python:
793
+ original_input = input_file
794
+ input_file, new_base_name = fix_module_name(input_file)
795
+ should_cleanup = new_base_name is not None
796
+ else:
797
+ original_input = input_file
798
+ should_cleanup = False
799
+
800
+ py_version = get_python_version()
801
+ py_include = get_python_include()
802
+ py_lib_path, py_lib_name = get_python_lib()
803
+
804
+ work_dir = os.path.expanduser('~/tmp/pycc_build')
805
+ os.makedirs(work_dir, exist_ok=True)
806
+
807
+ try:
808
+ base_name = os.path.splitext(os.path.basename(input_file))[0]
809
+ pyx_file = os.path.join(work_dir, f"{base_name}.pyx")
810
+ shutil.copy2(input_file, pyx_file)
811
+ print_debug(f"Copied: {input_file} -> {pyx_file}")
812
+
813
+ out_file = os.path.join(work_dir, f"{base_name}.c")
814
+ cython_cmd = ['cython', '--embed', '-3', pyx_file, '-o', out_file]
815
+
816
+ print_debug(f"Cython output: {out_file}")
817
+ print_cmd(cython_cmd)
818
+
819
+ if dry_run:
820
+ print(' '.join(cython_cmd))
821
+ compile_cmd = [
822
+ 'clang', f'-O{opt_level}', '-o', output if output else f"./{base_name}.out", out_file,
823
+ f'-I{py_include}', f'-L{py_lib_path}', f'-l{py_lib_name}',
824
+ '-lpthread', '-lm', '-lutil', '-ldl'
825
+ ] + other_flags
826
+ print(' '.join(compile_cmd))
827
+ return
828
+
829
+ result = subprocess.run(cython_cmd, capture_output=True, text=True)
830
+ if result.returncode != 0:
831
+ print_error("Cython error:")
832
+ print(result.stderr, file=sys.stderr)
833
+ sys.exit(1)
834
+
835
+ print_debug("Cython successful")
836
+
837
+ if run:
838
+ temp_output = os.path.join(work_dir, f"{base_name}.out")
839
+ final_output = temp_output
840
+ else:
841
+ if output:
842
+ final_output = output
843
+ else:
844
+ final_output = f"./{base_name}.out"
845
+
846
+ compile_cmd = [
847
+ 'clang', f'-O{opt_level}', '-o', final_output, out_file,
848
+ f'-I{py_include}', f'-L{py_lib_path}', f'-l{py_lib_name}',
849
+ '-lpthread', '-lm', '-lutil', '-ldl'
850
+ ] + other_flags
851
+
852
+ print_cmd(compile_cmd)
853
+ result = subprocess.run(compile_cmd, capture_output=True, text=True)
854
+ if result.returncode != 0:
855
+ print_error("Compilation error:")
856
+ print(result.stderr, file=sys.stderr)
857
+ sys.exit(1)
858
+
859
+ os.chmod(final_output, 0o755)
860
+ print_debug(f"Permissions set: {final_output}")
861
+
862
+ add_compile_marker(final_output)
863
+
864
+ if run:
865
+ cmd = [final_output] + script_args
866
+ print_debug(f"Running: {' '.join(cmd)}")
867
+ os.system(' '.join(cmd))
868
+
869
+ finally:
870
+ if os.path.exists(work_dir) and not dry_run:
871
+ shutil.rmtree(work_dir)
872
+ print_debug(f"Cleaned: {work_dir}")
873
+ if should_cleanup and os.path.exists(input_file):
874
+ os.remove(input_file)
875
+ print_debug(f"Cleaned: {input_file}")
876
+
877
+ if __name__ == '__main__':
878
+ main()
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: pycc-compiler
3
+ Version: 1.4.6
4
+ Summary: Compile Python to ELF executable
5
+ Home-page: https://github.com/yourname/pycc
6
+ Author: pycc contributors
7
+ Author-email:
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: Android
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Environment :: Console
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: cython>=3.0.0
17
+ Dynamic: author
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: requires-dist
23
+ Dynamic: requires-python
24
+ Dynamic: summary
25
+
26
+ # pycc - Python to ELF Compiler
27
+
28
+ A tool to compile Python/Pyrex files to standalone executables or shared libraries.
29
+
30
+ ## Features
31
+
32
+ - Compile `.py` and `.pyx` files to ELF executables
33
+ - Generate shared libraries (`.so`) for Python imports
34
+ - Support `-c` to generate C files, `-cpp` for C++ files
35
+ - Interactive mode for rapid prototyping
36
+ - Optimize with `-O0` to `-O3`
37
+ - Static compilation with clang
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install pycc-compiler
@@ -0,0 +1,10 @@
1
+ README.md
2
+ setup.py
3
+ pycc/__init__.py
4
+ pycc/pycc.py
5
+ pycc_compiler.egg-info/PKG-INFO
6
+ pycc_compiler.egg-info/SOURCES.txt
7
+ pycc_compiler.egg-info/dependency_links.txt
8
+ pycc_compiler.egg-info/entry_points.txt
9
+ pycc_compiler.egg-info/requires.txt
10
+ pycc_compiler.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pycc = pycc.pycc:main
@@ -0,0 +1 @@
1
+ cython>=3.0.0
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env python3
2
+ from setuptools import setup, find_packages
3
+ import os
4
+
5
+ with open("README.md", "r", encoding="utf-8") as fh:
6
+ long_description = fh.read()
7
+
8
+ with open("pycc/__init__.py", "r", encoding="utf-8") as f:
9
+ for line in f:
10
+ if line.startswith("__version__"):
11
+ version = line.split("=")[1].strip().strip('"')
12
+ break
13
+ else:
14
+ version = "1.4.6"
15
+
16
+ setup(
17
+ name="pycc-compiler",
18
+ version=version,
19
+ author="pycc contributors",
20
+ author_email="",
21
+ description="Compile Python to ELF executable",
22
+ long_description=long_description,
23
+ long_description_content_type="text/markdown",
24
+ url="https://github.com/yourname/pycc",
25
+ packages=find_packages(),
26
+ classifiers=[
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.13",
29
+ "License :: OSI Approved :: MIT License",
30
+ "Operating System :: Android",
31
+ "Operating System :: POSIX :: Linux",
32
+ "Environment :: Console",
33
+ ],
34
+ python_requires=">=3.8",
35
+ entry_points={
36
+ "console_scripts": [
37
+ "pycc=pycc.pycc:main",
38
+ ],
39
+ },
40
+ install_requires=[
41
+ "cython>=3.0.0",
42
+ ],
43
+ )