pysfi 0.1.5__py3-none-any.whl
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.
- pysfi-0.1.5.dist-info/METADATA +107 -0
- pysfi-0.1.5.dist-info/RECORD +19 -0
- pysfi-0.1.5.dist-info/WHEEL +4 -0
- pysfi-0.1.5.dist-info/entry_points.txt +11 -0
- sfi/__init__.py +3 -0
- sfi/alarmclock/__init__.py +0 -0
- sfi/alarmclock/alarmclock.py +367 -0
- sfi/bumpversion/__init__.py +3 -0
- sfi/bumpversion/bumpversion.py +535 -0
- sfi/embedinstall/embedinstall.py +418 -0
- sfi/filedate/__init__.py +0 -0
- sfi/filedate/filedate.py +112 -0
- sfi/makepython/__init__.py +0 -0
- sfi/makepython/makepython.py +310 -0
- sfi/projectparse/projectparse.py +152 -0
- sfi/pyloadergen/pyloadergen.py +995 -0
- sfi/pypacker/fspacker.py +91 -0
- sfi/taskkill/taskkill.py +236 -0
- sfi/which/which.py +74 -0
|
@@ -0,0 +1,995 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
import platform
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
is_windows = platform.system() == "Windows"
|
|
12
|
+
is_linux = platform.system() == "Linux"
|
|
13
|
+
is_macos = platform.system() == "Darwin"
|
|
14
|
+
ext = ".exe" if is_windows else ""
|
|
15
|
+
|
|
16
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
cwd = Path.cwd()
|
|
19
|
+
|
|
20
|
+
_WINDOWS_GUI_TEMPLATE: str = r"""#include <windows.h>
|
|
21
|
+
#include <stdio.h>
|
|
22
|
+
#include <stdlib.h>
|
|
23
|
+
#include <string.h>
|
|
24
|
+
#include <locale.h>
|
|
25
|
+
|
|
26
|
+
#define MAX_PATH_LEN 4096
|
|
27
|
+
#define MAX_ERROR_LEN 8192
|
|
28
|
+
|
|
29
|
+
// Check if Python runtime exists
|
|
30
|
+
static int check_python_runtime(const char* runtime_path) {
|
|
31
|
+
return GetFileAttributesA(runtime_path) != INVALID_FILE_ATTRIBUTES;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Build Python command line
|
|
35
|
+
static void build_python_command(
|
|
36
|
+
char* cmd,
|
|
37
|
+
const char* exe_dir,
|
|
38
|
+
const char* entry_file,
|
|
39
|
+
int is_debug
|
|
40
|
+
) {
|
|
41
|
+
char python_runtime[MAX_PATH_LEN];
|
|
42
|
+
char script_path[MAX_PATH_LEN];
|
|
43
|
+
|
|
44
|
+
// Build Python interpreter path
|
|
45
|
+
// GUI non-debug mode uses pythonw.exe (no console), other cases use python.exe
|
|
46
|
+
if (is_debug) {
|
|
47
|
+
// Debug mode: use python.exe to show console output
|
|
48
|
+
snprintf(python_runtime, MAX_PATH_LEN, "%s\\runtime\\python.exe", exe_dir);
|
|
49
|
+
} else {
|
|
50
|
+
// Production GUI mode: use pythonw.exe without creating console window
|
|
51
|
+
snprintf(python_runtime, MAX_PATH_LEN, "%s\\runtime\\pythonw.exe", exe_dir);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Build startup script path
|
|
55
|
+
snprintf(script_path, MAX_PATH_LEN, "%s\\%s", exe_dir, entry_file);
|
|
56
|
+
|
|
57
|
+
// Build command line (add -u parameter for real-time output capture)
|
|
58
|
+
if (is_debug) {
|
|
59
|
+
// Debug mode: do not redirect output, display output on console
|
|
60
|
+
snprintf(cmd, MAX_PATH_LEN, "\"%s\" -u \"%s\"", python_runtime, script_path);
|
|
61
|
+
} else {
|
|
62
|
+
// Production mode: redirect all output to pipe
|
|
63
|
+
snprintf(cmd, MAX_PATH_LEN, "\"%s\" -u \"%s\" 2>&1", python_runtime, script_path);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Read process output
|
|
68
|
+
static void read_process_output(HANDLE hPipe, char* output, int max_len) {
|
|
69
|
+
DWORD bytes_read;
|
|
70
|
+
output[0] = '\0';
|
|
71
|
+
|
|
72
|
+
while (ReadFile(hPipe, output + strlen(output), max_len - strlen(output) - 1, &bytes_read, NULL) && bytes_read > 0) {
|
|
73
|
+
output[bytes_read] = '\0';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Show message box (support UTF-8 encoding)
|
|
78
|
+
static void show_message_box(const char* title, const char* message) {
|
|
79
|
+
// Convert UTF-8 to UTF-16 using MultiByteToWideChar
|
|
80
|
+
int title_len = MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0);
|
|
81
|
+
int msg_len = MultiByteToWideChar(CP_UTF8, 0, message, -1, NULL, 0);
|
|
82
|
+
|
|
83
|
+
if (title_len > 0 && msg_len > 0) {
|
|
84
|
+
wchar_t* wtitle = (wchar_t*)malloc(title_len * sizeof(wchar_t));
|
|
85
|
+
wchar_t* wmsg = (wchar_t*)malloc(msg_len * sizeof(wchar_t));
|
|
86
|
+
|
|
87
|
+
if (wtitle && wmsg) {
|
|
88
|
+
MultiByteToWideChar(CP_UTF8, 0, title, -1, wtitle, title_len);
|
|
89
|
+
MultiByteToWideChar(CP_UTF8, 0, message, -1, wmsg, msg_len);
|
|
90
|
+
MessageBoxW(NULL, wmsg, wtitle, MB_ICONERROR | MB_OK);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (wtitle) free(wtitle);
|
|
94
|
+
if (wmsg) free(wmsg);
|
|
95
|
+
} else {
|
|
96
|
+
// Conversion failed, use ANSI version
|
|
97
|
+
MessageBoxA(NULL, message, title, MB_ICONERROR | MB_OK);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Windows GUI entry point
|
|
102
|
+
int APIENTRY WinMain(
|
|
103
|
+
HINSTANCE hInstance,
|
|
104
|
+
HINSTANCE hPrevInstance,
|
|
105
|
+
LPSTR lpCmdLine,
|
|
106
|
+
int nCmdShow
|
|
107
|
+
) {
|
|
108
|
+
char exe_dir[MAX_PATH_LEN];
|
|
109
|
+
char cmd[MAX_PATH_LEN * 2];
|
|
110
|
+
char error_output[MAX_ERROR_LEN] = "";
|
|
111
|
+
STARTUPINFOA si = {0};
|
|
112
|
+
PROCESS_INFORMATION pi = {0};
|
|
113
|
+
SECURITY_ATTRIBUTES sa = {0};
|
|
114
|
+
HANDLE hReadPipe = NULL, hWritePipe = NULL;
|
|
115
|
+
BOOL success;
|
|
116
|
+
|
|
117
|
+
// Get executable directory
|
|
118
|
+
GetModuleFileNameA(NULL, exe_dir, MAX_PATH_LEN);
|
|
119
|
+
char* last_slash = strrchr(exe_dir, '\\');
|
|
120
|
+
if (last_slash) {
|
|
121
|
+
*last_slash = '\0';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check Python runtime
|
|
125
|
+
if (!check_python_runtime(exe_dir)) {
|
|
126
|
+
show_message_box(
|
|
127
|
+
"Application Error",
|
|
128
|
+
"Python runtime not found.\n\n"
|
|
129
|
+
"Please ensure the application is installed correctly and the runtime directory exists."
|
|
130
|
+
);
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Build and execute Python command
|
|
135
|
+
build_python_command(cmd, exe_dir, "${ENTRY_FILE}", ${DEBUG_MODE});
|
|
136
|
+
|
|
137
|
+
// Create pipe for capturing output
|
|
138
|
+
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
139
|
+
sa.bInheritHandle = TRUE;
|
|
140
|
+
sa.lpSecurityDescriptor = NULL;
|
|
141
|
+
|
|
142
|
+
if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) {
|
|
143
|
+
show_message_box("Error", "Failed to create pipe for error output.");
|
|
144
|
+
return 1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Set startup info
|
|
148
|
+
si.cb = sizeof(si);
|
|
149
|
+
|
|
150
|
+
// Set different startup methods based on debug mode
|
|
151
|
+
if (${DEBUG_MODE}) {
|
|
152
|
+
// Debug mode: do not use pipe, inherit console to display output
|
|
153
|
+
// Keep si.dwFlags as 0, do not set STARTF_USESTDHANDLES
|
|
154
|
+
} else {
|
|
155
|
+
// Production GUI mode: use pipe to capture error output
|
|
156
|
+
si.dwFlags = STARTF_USESTDHANDLES;
|
|
157
|
+
si.hStdError = hWritePipe;
|
|
158
|
+
si.hStdOutput = hWritePipe;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Create Python process
|
|
162
|
+
// Debug mode: do not use CREATE_NO_WINDOW, let python.exe create console
|
|
163
|
+
// Production GUI mode: use CREATE_NO_WINDOW to ensure no console is created
|
|
164
|
+
DWORD createFlags = ${DEBUG_MODE} ? 0 : CREATE_NO_WINDOW;
|
|
165
|
+
success = CreateProcessA(
|
|
166
|
+
NULL, cmd, NULL, NULL, FALSE,
|
|
167
|
+
createFlags,
|
|
168
|
+
NULL, exe_dir, &si, &pi
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (!success) {
|
|
172
|
+
DWORD error = GetLastError();
|
|
173
|
+
char error_msg[MAX_ERROR_LEN];
|
|
174
|
+
snprintf(error_msg, MAX_ERROR_LEN,
|
|
175
|
+
"Failed to start Python process.\n\n"
|
|
176
|
+
"Error code: %lu\n"
|
|
177
|
+
"Command: %s",
|
|
178
|
+
error, cmd);
|
|
179
|
+
show_message_box("Application Error", error_msg);
|
|
180
|
+
if (!${DEBUG_MODE}) {
|
|
181
|
+
CloseHandle(hWritePipe);
|
|
182
|
+
CloseHandle(hReadPipe);
|
|
183
|
+
}
|
|
184
|
+
return 1;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Read error output (only in non-debug mode)
|
|
188
|
+
if (!${DEBUG_MODE}) {
|
|
189
|
+
CloseHandle(hWritePipe);
|
|
190
|
+
read_process_output(hReadPipe, error_output, MAX_ERROR_LEN);
|
|
191
|
+
CloseHandle(hReadPipe);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Wait for process to end
|
|
195
|
+
WaitForSingleObject(pi.hProcess, INFINITE);
|
|
196
|
+
|
|
197
|
+
// Get exit code
|
|
198
|
+
DWORD exit_code;
|
|
199
|
+
GetExitCodeProcess(pi.hProcess, &exit_code);
|
|
200
|
+
|
|
201
|
+
// Debug output
|
|
202
|
+
fprintf(stderr, "DEBUG: Python process exited with code: %lu\n", exit_code);
|
|
203
|
+
|
|
204
|
+
// Cleanup
|
|
205
|
+
CloseHandle(pi.hProcess);
|
|
206
|
+
CloseHandle(pi.hThread);
|
|
207
|
+
|
|
208
|
+
// If process exits abnormally and there is error output, display error message
|
|
209
|
+
if (exit_code != 0 && strlen(error_output) > 0) {
|
|
210
|
+
// Truncate the last error message (up to 2000 characters)
|
|
211
|
+
size_t error_len = strlen(error_output);
|
|
212
|
+
if (error_len > 2000) {
|
|
213
|
+
error_output[2000] = '\0';
|
|
214
|
+
}
|
|
215
|
+
show_message_box("Application Error", error_output);
|
|
216
|
+
return exit_code;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return exit_code;
|
|
220
|
+
}
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
_WINDOWS_CONSOLE_TEMPLATE: str = r"""#include <stdio.h>
|
|
224
|
+
#include <stdlib.h>
|
|
225
|
+
#include <string.h>
|
|
226
|
+
#include <windows.h>
|
|
227
|
+
#include <locale.h>
|
|
228
|
+
|
|
229
|
+
#define MAX_PATH_LEN 4096
|
|
230
|
+
|
|
231
|
+
// Set console encoding to UTF-8
|
|
232
|
+
static void setup_encoding() {
|
|
233
|
+
SetConsoleOutputCP(CP_UTF8);
|
|
234
|
+
SetConsoleCP(CP_UTF8);
|
|
235
|
+
setlocale(LC_ALL, ".UTF8");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check if Python runtime exists
|
|
239
|
+
static int check_python_runtime(const char* runtime_path) {
|
|
240
|
+
return GetFileAttributesA(runtime_path) != INVALID_FILE_ATTRIBUTES;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Build Python command line
|
|
244
|
+
static void build_python_command(
|
|
245
|
+
char* cmd,
|
|
246
|
+
const char* exe_dir,
|
|
247
|
+
const char* entry_file,
|
|
248
|
+
int debug_mode
|
|
249
|
+
) {
|
|
250
|
+
char python_runtime[MAX_PATH_LEN];
|
|
251
|
+
char script_path[MAX_PATH_LEN];
|
|
252
|
+
|
|
253
|
+
// Build Python interpreter path
|
|
254
|
+
snprintf(python_runtime, MAX_PATH_LEN, "%s\\runtime\\python.exe", exe_dir);
|
|
255
|
+
|
|
256
|
+
// Build startup script path
|
|
257
|
+
snprintf(script_path, MAX_PATH_LEN, "%s\\%s", exe_dir, entry_file);
|
|
258
|
+
|
|
259
|
+
// Build command line (add -u parameter for real-time output capture)
|
|
260
|
+
if (debug_mode) {
|
|
261
|
+
snprintf(cmd, MAX_PATH_LEN, "\"%s\" \"%s\"", python_runtime, script_path);
|
|
262
|
+
} else {
|
|
263
|
+
snprintf(cmd, MAX_PATH_LEN, "\"%s\" -u \"%s\" 2>&1", python_runtime, script_path);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Windows Console entry point
|
|
268
|
+
int main(int argc, char* argv[]) {
|
|
269
|
+
char exe_dir[MAX_PATH_LEN];
|
|
270
|
+
char cmd[MAX_PATH_LEN * 2];
|
|
271
|
+
STARTUPINFOA si = {0};
|
|
272
|
+
PROCESS_INFORMATION pi = {0};
|
|
273
|
+
|
|
274
|
+
// Set encoding to UTF-8
|
|
275
|
+
setup_encoding();
|
|
276
|
+
|
|
277
|
+
// Get executable directory
|
|
278
|
+
GetModuleFileNameA(NULL, exe_dir, MAX_PATH_LEN);
|
|
279
|
+
char* last_slash = strrchr(exe_dir, '\\');
|
|
280
|
+
if (last_slash) {
|
|
281
|
+
*last_slash = '\0';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Check Python runtime
|
|
285
|
+
char python_runtime_check[MAX_PATH_LEN];
|
|
286
|
+
snprintf(python_runtime_check, MAX_PATH_LEN, "%s\\runtime\\python.exe", exe_dir);
|
|
287
|
+
if (!check_python_runtime(python_runtime_check)) {
|
|
288
|
+
fprintf(stderr, "Error: Python runtime not found at %s\\runtime\\\n", exe_dir);
|
|
289
|
+
fprintf(stderr, "Please ensure the application is installed correctly.\n");
|
|
290
|
+
return 1;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Build and execute Python command
|
|
294
|
+
build_python_command(cmd, exe_dir, "${ENTRY_FILE}", ${DEBUG_MODE});
|
|
295
|
+
|
|
296
|
+
// Debug output
|
|
297
|
+
if (${DEBUG_MODE}) {
|
|
298
|
+
fprintf(stderr, "DEBUG: Command to execute: %s\n", cmd);
|
|
299
|
+
fprintf(stderr, "DEBUG: exe_dir: %s\n", exe_dir);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Set startup info to inherit console
|
|
303
|
+
si.cb = sizeof(si);
|
|
304
|
+
|
|
305
|
+
// Ensure standard handles are inherited so output displays on console
|
|
306
|
+
si.dwFlags = STARTF_USESTDHANDLES;
|
|
307
|
+
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
|
308
|
+
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
309
|
+
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
|
310
|
+
|
|
311
|
+
// Create Python process
|
|
312
|
+
if (!CreateProcessA(
|
|
313
|
+
NULL, cmd, NULL, NULL, TRUE, // TRUE to inherit standard handles
|
|
314
|
+
0, NULL, exe_dir, &si, &pi
|
|
315
|
+
)) {
|
|
316
|
+
DWORD error = GetLastError();
|
|
317
|
+
fprintf(stderr, "Error: Failed to start Python process.\n");
|
|
318
|
+
fprintf(stderr, "Error code: %lu\n", error);
|
|
319
|
+
fprintf(stderr, "Command: %s\n", cmd);
|
|
320
|
+
return 1;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Wait for process to end
|
|
324
|
+
WaitForSingleObject(pi.hProcess, INFINITE);
|
|
325
|
+
|
|
326
|
+
// Get exit code
|
|
327
|
+
DWORD exit_code;
|
|
328
|
+
GetExitCodeProcess(pi.hProcess, &exit_code);
|
|
329
|
+
|
|
330
|
+
// Debug output
|
|
331
|
+
if (${DEBUG_MODE}) {
|
|
332
|
+
fprintf(stderr, "DEBUG: Python process exited with code: %lu\n", exit_code);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Cleanup
|
|
336
|
+
CloseHandle(pi.hProcess);
|
|
337
|
+
CloseHandle(pi.hThread);
|
|
338
|
+
|
|
339
|
+
// If process exits abnormally, display prompt
|
|
340
|
+
if (exit_code != 0 && !${DEBUG_MODE}) {
|
|
341
|
+
fprintf(stderr, "\nApplication exited abnormally, error code: %lu\n", exit_code);
|
|
342
|
+
fprintf(stderr, "Please check the error information above for details.\n");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return exit_code;
|
|
346
|
+
}
|
|
347
|
+
"""
|
|
348
|
+
|
|
349
|
+
_UNIX_GUI_TEMPLATE: str = r"""#define _GNU_SOURCE
|
|
350
|
+
#include <stdio.h>
|
|
351
|
+
#include <stdlib.h>
|
|
352
|
+
#include <string.h>
|
|
353
|
+
#include <unistd.h>
|
|
354
|
+
#include <sys/stat.h>
|
|
355
|
+
#include <sys/wait.h>
|
|
356
|
+
|
|
357
|
+
#define MAX_PATH_LEN 4096
|
|
358
|
+
#define MAX_ERROR_LEN 8192
|
|
359
|
+
|
|
360
|
+
// Build Python command line
|
|
361
|
+
static void build_python_command(
|
|
362
|
+
char* cmd,
|
|
363
|
+
const char* exe_dir,
|
|
364
|
+
const char* entry_file
|
|
365
|
+
) {
|
|
366
|
+
char script_path[MAX_PATH_LEN];
|
|
367
|
+
|
|
368
|
+
// Build startup script path
|
|
369
|
+
snprintf(script_path, MAX_PATH_LEN * 2, "%s/%s", exe_dir, entry_file);
|
|
370
|
+
|
|
371
|
+
// Build log file path (record error information)
|
|
372
|
+
char log_file[MAX_PATH_LEN];
|
|
373
|
+
snprintf(log_file, MAX_PATH_LEN, "%s/.error_log", exe_dir);
|
|
374
|
+
|
|
375
|
+
// Build command line - use system python3 instead of bundled runtime
|
|
376
|
+
// GUI mode uses nohup and background execution, error output to log
|
|
377
|
+
snprintf(cmd, MAX_PATH_LEN * 2,
|
|
378
|
+
"cd \"%s\" && nohup python3 -u \"%s\" >\"%s\" 2>&1 &",
|
|
379
|
+
exe_dir, script_path, log_file
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Unix GUI entry point
|
|
384
|
+
int main(int argc, char* argv[]) {
|
|
385
|
+
char exe_dir[MAX_PATH_LEN];
|
|
386
|
+
char cmd[MAX_PATH_LEN * 2];
|
|
387
|
+
char log_file[MAX_PATH_LEN];
|
|
388
|
+
pid_t pid;
|
|
389
|
+
int status;
|
|
390
|
+
|
|
391
|
+
// Get executable directory
|
|
392
|
+
if (realpath("/proc/self/exe", exe_dir) == NULL) {
|
|
393
|
+
// If /proc/self/exe is unavailable (like on macOS), use argv[0]
|
|
394
|
+
if (realpath(argv[0], exe_dir) == NULL) {
|
|
395
|
+
fprintf(stderr, "Error: Cannot determine executable directory\n");
|
|
396
|
+
return 1;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Remove executable name, keep only directory
|
|
401
|
+
char* last_slash = strrchr(exe_dir, '/');
|
|
402
|
+
if (last_slash) {
|
|
403
|
+
*last_slash = '\0';
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Build and execute Python command
|
|
407
|
+
build_python_command(cmd, exe_dir, "${ENTRY_FILE}");
|
|
408
|
+
snprintf(log_file, MAX_PATH_LEN, "%s/.error_log", exe_dir);
|
|
409
|
+
|
|
410
|
+
// Execute Python process in background
|
|
411
|
+
pid = fork();
|
|
412
|
+
if (pid == 0) {
|
|
413
|
+
// Child process: execute Python command and exit immediately
|
|
414
|
+
int ret = system(cmd);
|
|
415
|
+
_exit(WEXITSTATUS(ret));
|
|
416
|
+
} else if (pid < 0) {
|
|
417
|
+
fprintf(stderr, "Error: Failed to fork process\n");
|
|
418
|
+
return 1;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Wait for a short time to check if process failed to start
|
|
422
|
+
sleep(1);
|
|
423
|
+
if (waitpid(pid, &status, WNOHANG) == pid) {
|
|
424
|
+
// Process has already exited, read error log
|
|
425
|
+
FILE* fp = fopen(log_file, "r");
|
|
426
|
+
if (fp) {
|
|
427
|
+
char error_msg[MAX_ERROR_LEN];
|
|
428
|
+
if (fgets(error_msg, sizeof(error_msg), fp)) {
|
|
429
|
+
fprintf(stderr, "Application failed to start:\n%s\n", error_msg);
|
|
430
|
+
}
|
|
431
|
+
fclose(fp);
|
|
432
|
+
} else {
|
|
433
|
+
fprintf(stderr, "Application failed to start (exit code: %d)\n", WEXITSTATUS(status));
|
|
434
|
+
}
|
|
435
|
+
return WEXITSTATUS(status);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Parent process exits immediately
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
441
|
+
"""
|
|
442
|
+
|
|
443
|
+
_UNIX_CONSOLE_TEMPLATE: str = r"""#define _GNU_SOURCE
|
|
444
|
+
#include <stdio.h>
|
|
445
|
+
#include <stdlib.h>
|
|
446
|
+
#include <string.h>
|
|
447
|
+
#include <unistd.h>
|
|
448
|
+
#include <sys/stat.h>
|
|
449
|
+
#include <sys/wait.h>
|
|
450
|
+
|
|
451
|
+
#define MAX_PATH_LEN 4096
|
|
452
|
+
|
|
453
|
+
// Build Python command line
|
|
454
|
+
static void build_python_command(
|
|
455
|
+
char* cmd,
|
|
456
|
+
const char* exe_dir,
|
|
457
|
+
const char* entry_file
|
|
458
|
+
) {
|
|
459
|
+
char script_path[MAX_PATH_LEN];
|
|
460
|
+
|
|
461
|
+
// Build startup script path
|
|
462
|
+
snprintf(script_path, MAX_PATH_LEN, "%s/%s", exe_dir, entry_file);
|
|
463
|
+
|
|
464
|
+
// Build command line - use system python3 instead of bundled runtime
|
|
465
|
+
// add -u parameter for real-time output capture
|
|
466
|
+
snprintf(cmd, MAX_PATH_LEN * 2,
|
|
467
|
+
"cd \"%s\" && python3 -u \"%s\"",
|
|
468
|
+
exe_dir, script_path
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Unix Console entry point
|
|
473
|
+
int main(int argc, char* argv[]) {
|
|
474
|
+
char exe_dir[MAX_PATH_LEN];
|
|
475
|
+
char cmd[MAX_PATH_LEN * 2];
|
|
476
|
+
pid_t pid;
|
|
477
|
+
int status;
|
|
478
|
+
|
|
479
|
+
// Get executable directory
|
|
480
|
+
if (realpath("/proc/self/exe", exe_dir) == NULL) {
|
|
481
|
+
fprintf(stderr, "Error: Cannot determine executable directory\n");
|
|
482
|
+
return 1;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Remove executable name, keep only directory
|
|
486
|
+
char* last_slash = strrchr(exe_dir, '/');
|
|
487
|
+
if (last_slash) {
|
|
488
|
+
*last_slash = '\0';
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Build and execute Python command
|
|
492
|
+
build_python_command(cmd, exe_dir, "${ENTRY_FILE}");
|
|
493
|
+
|
|
494
|
+
// Fork and execute Python process
|
|
495
|
+
pid = fork();
|
|
496
|
+
if (pid == 0) {
|
|
497
|
+
// Child process: execute Python command
|
|
498
|
+
execl("/bin/sh", "sh", "-c", cmd, NULL);
|
|
499
|
+
fprintf(stderr, "Error: Failed to execute Python process\n");
|
|
500
|
+
_exit(127); // If execl fails
|
|
501
|
+
} else if (pid < 0) {
|
|
502
|
+
fprintf(stderr, "Error: Failed to fork process\n");
|
|
503
|
+
return 1;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Parent process: wait for child process to end
|
|
507
|
+
waitpid(pid, &status, 0);
|
|
508
|
+
|
|
509
|
+
int exit_code = WEXITSTATUS(status);
|
|
510
|
+
if (exit_code != 0) {
|
|
511
|
+
fprintf(stderr, "\nApplication exited abnormally, error code: %d\n", exit_code);
|
|
512
|
+
fprintf(stderr, "Please check the error information above for details.\n");
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return exit_code;
|
|
516
|
+
}
|
|
517
|
+
"""
|
|
518
|
+
|
|
519
|
+
_MACOS_GUI_TEMPLATE: str = r"""#define _GNU_SOURCE
|
|
520
|
+
#include <stdio.h>
|
|
521
|
+
#include <stdlib.h>
|
|
522
|
+
#include <string.h>
|
|
523
|
+
#include <unistd.h>
|
|
524
|
+
#include <sys/stat.h>
|
|
525
|
+
#include <sys/wait.h>
|
|
526
|
+
#include <CoreFoundation/CoreFoundation.h>
|
|
527
|
+
#include <ApplicationServices/ApplicationServices.h>
|
|
528
|
+
|
|
529
|
+
#define MAX_PATH_LEN 4096
|
|
530
|
+
#define MAX_ERROR_LEN 8192
|
|
531
|
+
|
|
532
|
+
// Check if Python interpreter exists
|
|
533
|
+
static int check_python_runtime(const char* runtime_path) {
|
|
534
|
+
struct stat st;
|
|
535
|
+
return stat(runtime_path, &st) == 0 && (st.st_mode & S_IXUSR);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Get executable directory (macOS specific)
|
|
539
|
+
static void get_exe_dir_macos(char* exe_dir, size_t size) {
|
|
540
|
+
CFBundleRef bundle = CFBundleGetMainBundle();
|
|
541
|
+
if (bundle) {
|
|
542
|
+
CFURLRef url = CFBundleCopyBundleURL(bundle);
|
|
543
|
+
if (url) {
|
|
544
|
+
CFStringRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
|
|
545
|
+
if (path) {
|
|
546
|
+
CFStringGetCString(path, exe_dir, size, kCFStringEncodingUTF8);
|
|
547
|
+
CFRelease(path);
|
|
548
|
+
}
|
|
549
|
+
CFRelease(url);
|
|
550
|
+
}
|
|
551
|
+
} else {
|
|
552
|
+
// If not in bundle, use argv[0]
|
|
553
|
+
realpath("./", exe_dir);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Remove Contents/MacOS suffix of bundle
|
|
557
|
+
char* macos_path = strstr(exe_dir, ".app/Contents/MacOS");
|
|
558
|
+
if (macos_path) {
|
|
559
|
+
*macos_path = '\0';
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Display macOS error dialog
|
|
564
|
+
static void show_error_dialog(const char* message) {
|
|
565
|
+
CFStringRef cf_message = CFStringCreateWithCString(
|
|
566
|
+
NULL, message, kCFStringEncodingUTF8
|
|
567
|
+
);
|
|
568
|
+
CFOptionFlags response;
|
|
569
|
+
CFUserNotificationDisplayAlert(
|
|
570
|
+
0,
|
|
571
|
+
kCFUserNotificationStopAlertLevel,
|
|
572
|
+
NULL, NULL, NULL,
|
|
573
|
+
CFSTR("Application Error"),
|
|
574
|
+
cf_message,
|
|
575
|
+
CFSTR("OK"), NULL, NULL, &response
|
|
576
|
+
);
|
|
577
|
+
CFRelease(cf_message);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Build Python command line
|
|
581
|
+
static void build_python_command(
|
|
582
|
+
char* cmd,
|
|
583
|
+
const char* exe_dir,
|
|
584
|
+
const char* entry_file
|
|
585
|
+
) {
|
|
586
|
+
char python_runtime[MAX_PATH_LEN];
|
|
587
|
+
char script_path[MAX_PATH_LEN];
|
|
588
|
+
char log_file[MAX_PATH_LEN];
|
|
589
|
+
|
|
590
|
+
// macOS Python path
|
|
591
|
+
snprintf(python_runtime, MAX_PATH_LEN, "%s/runtime/bin/python3", exe_dir);
|
|
592
|
+
snprintf(script_path, MAX_PATH_LEN, "%s/%s", exe_dir, entry_file);
|
|
593
|
+
|
|
594
|
+
// Build log file path
|
|
595
|
+
snprintf(log_file, MAX_PATH_LEN, "%s/.error_log", exe_dir);
|
|
596
|
+
|
|
597
|
+
// Build command line (GUI mode uses nohup and background execution, error output to log)
|
|
598
|
+
snprintf(cmd, MAX_PATH_LEN * 2,
|
|
599
|
+
"cd \"%s\" && nohup \"%s\" -u \"%s\" >\"%s\" 2>&1 &",
|
|
600
|
+
exe_dir, python_runtime, script_path, log_file
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// macOS GUI entry point
|
|
605
|
+
int main(int argc, char* argv[]) {
|
|
606
|
+
char exe_dir[MAX_PATH_LEN];
|
|
607
|
+
char cmd[MAX_PATH_LEN * 2];
|
|
608
|
+
char log_file[MAX_PATH_LEN];
|
|
609
|
+
pid_t pid;
|
|
610
|
+
int status;
|
|
611
|
+
|
|
612
|
+
// Get executable directory
|
|
613
|
+
get_exe_dir_macos(exe_dir, MAX_PATH_LEN);
|
|
614
|
+
|
|
615
|
+
// Check Python runtime
|
|
616
|
+
char python_runtime[MAX_PATH_LEN];
|
|
617
|
+
snprintf(python_runtime, MAX_PATH_LEN, "%s/runtime/bin/python3", exe_dir);
|
|
618
|
+
if (!check_python_runtime(python_runtime)) {
|
|
619
|
+
// macOS GUI app needs to use dialog to display error
|
|
620
|
+
char error_msg[MAX_PATH_LEN];
|
|
621
|
+
snprintf(error_msg, MAX_PATH_LEN,
|
|
622
|
+
"Python runtime not found.\n\n"
|
|
623
|
+
"Please ensure the application is installed correctly and the runtime directory is at:\n%s/runtime/bin/",
|
|
624
|
+
exe_dir);
|
|
625
|
+
show_error_dialog(error_msg);
|
|
626
|
+
return 1;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Build and execute Python command
|
|
630
|
+
build_python_command(cmd, exe_dir, "${ENTRY_FILE}");
|
|
631
|
+
snprintf(log_file, MAX_PATH_LEN, "%s/.error_log", exe_dir);
|
|
632
|
+
|
|
633
|
+
// Execute Python process in background
|
|
634
|
+
pid = fork();
|
|
635
|
+
if (pid == 0) {
|
|
636
|
+
// Child process
|
|
637
|
+
int ret = system(cmd);
|
|
638
|
+
_exit(WEXITSTATUS(ret));
|
|
639
|
+
} else if (pid < 0) {
|
|
640
|
+
char error_msg[MAX_PATH_LEN];
|
|
641
|
+
snprintf(error_msg, MAX_PATH_LEN, "Failed to create process.\nError: %s", strerror(errno));
|
|
642
|
+
show_error_dialog(error_msg);
|
|
643
|
+
return 1;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Wait for a short time to check if process failed to start
|
|
647
|
+
sleep(1);
|
|
648
|
+
if (waitpid(pid, &status, WNOHANG) == pid) {
|
|
649
|
+
// Process has already exited, read error log
|
|
650
|
+
FILE* fp = fopen(log_file, "r");
|
|
651
|
+
if (fp) {
|
|
652
|
+
char error_msg[MAX_ERROR_LEN];
|
|
653
|
+
if (fgets(error_msg, sizeof(error_msg), fp)) {
|
|
654
|
+
// Remove newline
|
|
655
|
+
char* newline = strchr(error_msg, '\n');
|
|
656
|
+
if (newline) *newline = '\0';
|
|
657
|
+
char full_error[MAX_PATH_LEN + MAX_ERROR_LEN];
|
|
658
|
+
snprintf(full_error, sizeof(full_error),
|
|
659
|
+
"Application failed to start.\n\nError:\n%s", error_msg);
|
|
660
|
+
show_error_dialog(full_error);
|
|
661
|
+
}
|
|
662
|
+
fclose(fp);
|
|
663
|
+
} else {
|
|
664
|
+
char error_msg[MAX_PATH_LEN];
|
|
665
|
+
snprintf(error_msg, MAX_PATH_LEN,
|
|
666
|
+
"Application failed to start.\n\nExit code: %d",
|
|
667
|
+
WEXITSTATUS(status));
|
|
668
|
+
show_error_dialog(error_msg);
|
|
669
|
+
}
|
|
670
|
+
return WEXITSTATUS(status);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return 0;
|
|
674
|
+
}
|
|
675
|
+
"""
|
|
676
|
+
|
|
677
|
+
_MACOS_CONSOLE_TEMPLATE: str = r"""#define _GNU_SOURCE
|
|
678
|
+
#include <stdio.h>
|
|
679
|
+
#include <stdlib.h>
|
|
680
|
+
#include <string.h>
|
|
681
|
+
#include <unistd.h>
|
|
682
|
+
#include <sys/stat.h>
|
|
683
|
+
#include <sys/wait.h>
|
|
684
|
+
|
|
685
|
+
#define MAX_PATH_LEN 4096
|
|
686
|
+
|
|
687
|
+
// Check if Python interpreter exists
|
|
688
|
+
static int check_python_runtime(const char* runtime_path) {
|
|
689
|
+
struct stat st;
|
|
690
|
+
return stat(runtime_path, &st) == 0 && (st.st_mode & S_IXUSR);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Get executable directory (macOS specific)
|
|
694
|
+
static void get_exe_dir_macos(char* exe_dir, size_t size) {
|
|
695
|
+
CFBundleRef bundle = CFBundleGetMainBundle();
|
|
696
|
+
if (bundle) {
|
|
697
|
+
CFURLRef url = CFBundleCopyBundleURL(bundle);
|
|
698
|
+
if (url) {
|
|
699
|
+
CFStringRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
|
|
700
|
+
if (path) {
|
|
701
|
+
CFStringGetCString(path, exe_dir, size, kCFStringEncodingUTF8);
|
|
702
|
+
CFRelease(path);
|
|
703
|
+
}
|
|
704
|
+
CFRelease(url);
|
|
705
|
+
}
|
|
706
|
+
} else {
|
|
707
|
+
realpath("./", exe_dir);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Build Python command line
|
|
712
|
+
static void build_python_command(
|
|
713
|
+
char* cmd,
|
|
714
|
+
const char* exe_dir,
|
|
715
|
+
const char* entry_file
|
|
716
|
+
) {
|
|
717
|
+
char python_runtime[MAX_PATH_LEN];
|
|
718
|
+
char script_path[MAX_PATH_LEN];
|
|
719
|
+
|
|
720
|
+
snprintf(python_runtime, MAX_PATH_LEN, "%s/runtime/bin/python3", exe_dir);
|
|
721
|
+
snprintf(script_path, MAX_PATH_LEN, "%s/%s", exe_dir, entry_file);
|
|
722
|
+
|
|
723
|
+
// Build command line (add -u parameter for real-time output capture)
|
|
724
|
+
snprintf(cmd, MAX_PATH_LEN,
|
|
725
|
+
"cd \"%s\" && \"%s\" -u \"%s\"",
|
|
726
|
+
exe_dir, python_runtime, script_path
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// macOS Console entry point
|
|
731
|
+
int main(int argc, char* argv[]) {
|
|
732
|
+
char exe_dir[MAX_PATH_LEN];
|
|
733
|
+
char cmd[MAX_PATH_LEN * 2];
|
|
734
|
+
pid_t pid;
|
|
735
|
+
int status;
|
|
736
|
+
|
|
737
|
+
// Get executable directory
|
|
738
|
+
get_exe_dir_macos(exe_dir, MAX_PATH_LEN);
|
|
739
|
+
|
|
740
|
+
// Check Python runtime
|
|
741
|
+
char python_runtime[MAX_PATH_LEN];
|
|
742
|
+
snprintf(python_runtime, MAX_PATH_LEN, "%s/runtime/bin/python3", exe_dir);
|
|
743
|
+
if (!check_python_runtime(python_runtime)) {
|
|
744
|
+
fprintf(stderr, "Error: Python runtime not found at %s/runtime/bin/\n", exe_dir);
|
|
745
|
+
fprintf(stderr, "Please ensure the application is installed correctly.\n");
|
|
746
|
+
return 1;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Build and execute Python command
|
|
750
|
+
build_python_command(cmd, exe_dir, "${ENTRY_FILE}");
|
|
751
|
+
|
|
752
|
+
// Fork and execute Python process
|
|
753
|
+
pid = fork();
|
|
754
|
+
if (pid == 0) {
|
|
755
|
+
execl("/bin/sh", "sh", "-c", cmd, NULL);
|
|
756
|
+
fprintf(stderr, "Error: Failed to execute Python process\n");
|
|
757
|
+
_exit(127);
|
|
758
|
+
} else if (pid < 0) {
|
|
759
|
+
fprintf(stderr, "Error: Failed to create process: %s\n", strerror(errno));
|
|
760
|
+
return 1;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
waitpid(pid, &status, 0);
|
|
764
|
+
|
|
765
|
+
int exit_code = WEXITSTATUS(status);
|
|
766
|
+
if (exit_code != 0) {
|
|
767
|
+
fprintf(stderr, "\nApplication exited abnormally, error code: %d\n", exit_code);
|
|
768
|
+
fprintf(stderr, "Please check the error information above for details.\n");
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return exit_code;
|
|
772
|
+
}
|
|
773
|
+
"""
|
|
774
|
+
|
|
775
|
+
_COMPILER_OPTIONS: dict[str, list[str]] = {
|
|
776
|
+
"gcc": ["-std=c99", "-Wall", "-Wextra", "-pedantic", "-O2", "-D_GNU_SOURCE"],
|
|
777
|
+
"clang": ["-std=c99", "-Wall", "-Wextra", "-pedantic", "-O2", "-D_GNU_SOURCE"],
|
|
778
|
+
"cl": ["/std:c99", "/Wall", "/Wextra", "/pedantic", "/O2", "/D_GNU_SOURCE"],
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def find_compiler() -> str | None:
|
|
783
|
+
"""Find the path to the specified compiler."""
|
|
784
|
+
compilers = _COMPILER_OPTIONS.keys()
|
|
785
|
+
|
|
786
|
+
for compiler in compilers:
|
|
787
|
+
if shutil.which(compiler) is not None:
|
|
788
|
+
return compiler
|
|
789
|
+
|
|
790
|
+
logger.error("No compiler found")
|
|
791
|
+
return None
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def get_compiler_args(
|
|
795
|
+
compiler: str,
|
|
796
|
+
) -> list[str]:
|
|
797
|
+
"""Get the arguments for the specified compiler."""
|
|
798
|
+
compiler_name = Path(compiler).stem if "\\" in compiler or "/" in compiler else compiler
|
|
799
|
+
|
|
800
|
+
if compiler_name in ("gcc", "clang"):
|
|
801
|
+
return ["-std=c99", "-Wall", "-pedantic", "-Werror"]
|
|
802
|
+
elif compiler_name == "cl":
|
|
803
|
+
return ["/std:c99", "/Wall", "/WX", "/Werror"]
|
|
804
|
+
else:
|
|
805
|
+
return []
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def select_template(
|
|
809
|
+
loader_type: str,
|
|
810
|
+
is_debug: bool,
|
|
811
|
+
) -> str:
|
|
812
|
+
"""Select the appropriate C code template based on platform and type.
|
|
813
|
+
|
|
814
|
+
In debug mode, always use console template to ensure output is visible.
|
|
815
|
+
"""
|
|
816
|
+
# In debug mode, always use console template for visibility
|
|
817
|
+
if is_debug:
|
|
818
|
+
if is_windows:
|
|
819
|
+
return _WINDOWS_CONSOLE_TEMPLATE
|
|
820
|
+
elif is_macos:
|
|
821
|
+
return _MACOS_CONSOLE_TEMPLATE
|
|
822
|
+
else:
|
|
823
|
+
return _UNIX_CONSOLE_TEMPLATE
|
|
824
|
+
|
|
825
|
+
# In non-debug mode, use the requested template type
|
|
826
|
+
if is_windows:
|
|
827
|
+
if loader_type == "gui":
|
|
828
|
+
return _WINDOWS_GUI_TEMPLATE
|
|
829
|
+
else:
|
|
830
|
+
return _WINDOWS_CONSOLE_TEMPLATE
|
|
831
|
+
elif is_macos:
|
|
832
|
+
if loader_type == "gui":
|
|
833
|
+
return _MACOS_GUI_TEMPLATE
|
|
834
|
+
else:
|
|
835
|
+
return _MACOS_CONSOLE_TEMPLATE
|
|
836
|
+
else: # Linux and other Unix-like systems
|
|
837
|
+
if loader_type == "gui":
|
|
838
|
+
return _UNIX_GUI_TEMPLATE
|
|
839
|
+
else:
|
|
840
|
+
return _UNIX_CONSOLE_TEMPLATE
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def generate_c_source(
|
|
844
|
+
template: str,
|
|
845
|
+
entry_file: str,
|
|
846
|
+
is_debug: bool,
|
|
847
|
+
) -> str:
|
|
848
|
+
"""Generate C source code by replacing placeholders in template."""
|
|
849
|
+
# Replace placeholders
|
|
850
|
+
c_code = template.replace("${ENTRY_FILE}", entry_file)
|
|
851
|
+
c_code = c_code.replace("${DEBUG_MODE}", "1" if is_debug else "0")
|
|
852
|
+
return c_code
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
def compile_c_source(
|
|
856
|
+
c_source_path: str | Path,
|
|
857
|
+
output_filepath: Path,
|
|
858
|
+
compiler: str | None = None,
|
|
859
|
+
) -> bool:
|
|
860
|
+
"""Compile C source code to executable file."""
|
|
861
|
+
# Find compiler if not specified
|
|
862
|
+
if compiler is None:
|
|
863
|
+
compiler = find_compiler()
|
|
864
|
+
if compiler is None:
|
|
865
|
+
logger.error("Error: No suitable C compiler found (gcc/clang/cl required)")
|
|
866
|
+
return False
|
|
867
|
+
|
|
868
|
+
logger.debug(f"Using compiler: {compiler}")
|
|
869
|
+
|
|
870
|
+
# Get compiler arguments
|
|
871
|
+
compiler_args = get_compiler_args(compiler)
|
|
872
|
+
|
|
873
|
+
# Prepare paths using pathlib
|
|
874
|
+
c_source_path = Path(c_source_path)
|
|
875
|
+
|
|
876
|
+
output_filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
877
|
+
output_filepath = output_filepath.with_suffix(ext)
|
|
878
|
+
|
|
879
|
+
# Build compile command
|
|
880
|
+
compiler_name = Path(compiler).name if "\\" in compiler or "/" in compiler else compiler
|
|
881
|
+
if compiler_name.lower() == "cl" or compiler_name.lower() == "cl.exe":
|
|
882
|
+
# MSVC compiler
|
|
883
|
+
cmd = [
|
|
884
|
+
compiler,
|
|
885
|
+
*compiler_args,
|
|
886
|
+
str(c_source_path),
|
|
887
|
+
f"/Fe:{output_filepath}",
|
|
888
|
+
"/link",
|
|
889
|
+
"/SUBSYSTEM:WINDOWS" if "gui" in str(c_source_path).lower() else "/SUBSYSTEM:CONSOLE",
|
|
890
|
+
]
|
|
891
|
+
else:
|
|
892
|
+
# GCC/Clang compiler
|
|
893
|
+
subsystem_flag = []
|
|
894
|
+
# For Windows GUI, add -mwindows flag with gcc/clang
|
|
895
|
+
if is_windows and "gui" in str(c_source_path).lower():
|
|
896
|
+
subsystem_flag = ["-mwindows"]
|
|
897
|
+
cmd = [compiler, *compiler_args, *subsystem_flag, "-o", str(output_filepath), str(c_source_path)]
|
|
898
|
+
|
|
899
|
+
logger.debug(f"Compiling with command: {' '.join(cmd)}")
|
|
900
|
+
try:
|
|
901
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
902
|
+
if result.returncode != 0:
|
|
903
|
+
logger.debug(f"Compilation failed with return code {result.returncode}")
|
|
904
|
+
logger.debug(f"STDOUT: {result.stdout}")
|
|
905
|
+
logger.debug(f"STDERR: {result.stderr}")
|
|
906
|
+
return False
|
|
907
|
+
logger.info(f"Successfully compiled to: {output_filepath}")
|
|
908
|
+
return True
|
|
909
|
+
except FileNotFoundError:
|
|
910
|
+
logger.error(f"Error: Compiler '{compiler}' not found")
|
|
911
|
+
return False
|
|
912
|
+
except Exception as e:
|
|
913
|
+
logger.error(f"Error during compilation: {e}")
|
|
914
|
+
return False
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
def main():
|
|
918
|
+
parser = argparse.ArgumentParser(description="Generate a Python loader executable for current platform")
|
|
919
|
+
parser.add_argument("--debug", "-d", action="store_true", help="Enable debug mode")
|
|
920
|
+
parser.add_argument(
|
|
921
|
+
"-t", "--type", choices=["gui", "console"], default="console", help="Loader type (default: console)"
|
|
922
|
+
)
|
|
923
|
+
parser.add_argument("-o", "--output", default="", help="Output executable path (default: pyloader or pyloader.exe)")
|
|
924
|
+
parser.add_argument(
|
|
925
|
+
"-e", "--entry-file", default="main.py", help="Entry Python file path to execute (default: main.py)"
|
|
926
|
+
)
|
|
927
|
+
parser.add_argument(
|
|
928
|
+
"--compiler", help="Specify compiler to use. Examples: gcc, clang, cl, or full path like C:\\vc\\bin\\cl.exe"
|
|
929
|
+
)
|
|
930
|
+
parser.add_argument("--run", "-r", action="store_true", help="Run the generated executable after compilation")
|
|
931
|
+
|
|
932
|
+
args = parser.parse_args()
|
|
933
|
+
|
|
934
|
+
if args.debug:
|
|
935
|
+
logger.setLevel(logging.DEBUG)
|
|
936
|
+
|
|
937
|
+
# Determine default output file name based on platform and type
|
|
938
|
+
output_filepath = cwd / f"pyloader{ext}" if not args.output else Path(args.output)
|
|
939
|
+
|
|
940
|
+
# Select appropriate template (in debug mode, always use console)
|
|
941
|
+
template = select_template(args.type, args.debug)
|
|
942
|
+
|
|
943
|
+
# Generate C source code
|
|
944
|
+
c_code = generate_c_source(template, args.entry_file, args.debug)
|
|
945
|
+
|
|
946
|
+
# Write C source code to file
|
|
947
|
+
# File name should reflect actual template type, not just args.type
|
|
948
|
+
# (debug mode forces console template)
|
|
949
|
+
if args.debug:
|
|
950
|
+
c_source_file = "pyloader_debug_console.c"
|
|
951
|
+
elif args.type == "console":
|
|
952
|
+
c_source_file = "pyloader_console.c"
|
|
953
|
+
else: # gui type in non-debug mode
|
|
954
|
+
c_source_file = "pyloader_gui.c"
|
|
955
|
+
|
|
956
|
+
c_source_path = Path(c_source_file)
|
|
957
|
+
c_source_path.write_text(c_code, encoding="utf-8")
|
|
958
|
+
logger.info(f"Generated C source code: {c_source_path}")
|
|
959
|
+
|
|
960
|
+
t0 = time.perf_counter()
|
|
961
|
+
|
|
962
|
+
# Compile to executable
|
|
963
|
+
if compile_c_source(c_source_file, output_filepath, args.compiler):
|
|
964
|
+
logger.info("Loader executable generated successfully!")
|
|
965
|
+
logger.debug(f"Platform: {platform.system()}")
|
|
966
|
+
logger.debug(f"Type: {args.type}")
|
|
967
|
+
logger.debug(f"Debug mode: {args.debug}")
|
|
968
|
+
logger.debug(f"Python entry file: {args.entry_file}")
|
|
969
|
+
logger.debug(f"Output: {output_filepath}")
|
|
970
|
+
|
|
971
|
+
# Run the executable if --run flag is set
|
|
972
|
+
if args.run:
|
|
973
|
+
logger.debug("\nRunning the executable...")
|
|
974
|
+
import subprocess
|
|
975
|
+
|
|
976
|
+
try:
|
|
977
|
+
subprocess.run([str(Path(args.output))], check=True)
|
|
978
|
+
except subprocess.CalledProcessError as e:
|
|
979
|
+
logger.error(f"\nExecutable exited with code: {e.returncode}")
|
|
980
|
+
return e.returncode
|
|
981
|
+
except FileNotFoundError:
|
|
982
|
+
logger.error(f"\nError: Executable not found at {args.output}")
|
|
983
|
+
return 1
|
|
984
|
+
|
|
985
|
+
logger.info(f"Compilation time: {time.perf_counter() - t0:.4f} seconds")
|
|
986
|
+
return 0
|
|
987
|
+
else:
|
|
988
|
+
logger.error("\nFailed to compile loader executable")
|
|
989
|
+
return 1
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
if __name__ == "__main__":
|
|
993
|
+
import sys
|
|
994
|
+
|
|
995
|
+
sys.exit(main())
|