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.
- pycc_compiler-1.4.6/PKG-INFO +42 -0
- pycc_compiler-1.4.6/README.md +17 -0
- pycc_compiler-1.4.6/pycc/__init__.py +14 -0
- pycc_compiler-1.4.6/pycc/pycc.py +878 -0
- pycc_compiler-1.4.6/pycc_compiler.egg-info/PKG-INFO +42 -0
- pycc_compiler-1.4.6/pycc_compiler.egg-info/SOURCES.txt +10 -0
- pycc_compiler-1.4.6/pycc_compiler.egg-info/dependency_links.txt +1 -0
- pycc_compiler-1.4.6/pycc_compiler.egg-info/entry_points.txt +2 -0
- pycc_compiler-1.4.6/pycc_compiler.egg-info/requires.txt +1 -0
- pycc_compiler-1.4.6/pycc_compiler.egg-info/top_level.txt +1 -0
- pycc_compiler-1.4.6/setup.cfg +4 -0
- pycc_compiler-1.4.6/setup.py +43 -0
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cython>=3.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pycc
|
|
@@ -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
|
+
)
|