snapshot before regression test
This commit is contained in:
196
includes/subprocess.py
Normal file
196
includes/subprocess.py
Normal file
@@ -0,0 +1,196 @@
|
||||
import t, c
|
||||
from stdint import *
|
||||
import platmacro
|
||||
import memhub
|
||||
import string
|
||||
|
||||
# Windows 后端依赖
|
||||
import w32.win32base
|
||||
import w32.win32file
|
||||
import w32.win32process
|
||||
import w32.win32sync
|
||||
|
||||
# POSIX 后端依赖
|
||||
import posix
|
||||
|
||||
|
||||
# 子进程输出缓冲区大小
|
||||
OUTPUT_BUF_SIZE: t.CDefine = 65536
|
||||
|
||||
# 模块级全局 MBuddy 指针(由用户在 main 中赋值初始化)
|
||||
_mbuddy: memhub.MemBuddy | t.CPtr
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CompletedProcess 结果类
|
||||
# =============================================================================
|
||||
|
||||
class CompletedProcess:
|
||||
"""subprocess.run 的返回结果"""
|
||||
args: str # 原始命令行
|
||||
returncode: int # 进程退出码(0 表示成功)
|
||||
stdout: str # 标准输出(capture_output=True 时)
|
||||
stderr: str # 标准错误(capture_output=True 时)
|
||||
|
||||
def __new__(self, args: str, returncode: int, stdout: str, stderr: str) -> t.CPtr:
|
||||
# 堆分配(避免返回栈地址导致 use-after-return)
|
||||
return t.CPtr(_mbuddy.alloc(CompletedProcess.__sizeof__()))
|
||||
|
||||
def __init__(self, args: str, returncode: int, stdout: str, stderr: str):
|
||||
self.args = args
|
||||
self.returncode = returncode
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 入口函数
|
||||
# =============================================================================
|
||||
|
||||
def run(args: str, capture_output: bool = True, text: bool = True) -> CompletedProcess | t.CPtr:
|
||||
"""同步执行子进程并等待完成。
|
||||
args 为完整命令行字符串。
|
||||
capture_output=True 时捕获 stdout/stderr。
|
||||
text=True 时输出为字符串(当前实现总是返回字符串)。
|
||||
返回 CompletedProcess 对象。"""
|
||||
if platmacro.IS_WINDOWS:
|
||||
return _run_win32(args, capture_output, text)
|
||||
return _run_posix(args, capture_output, text)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Windows 后端实现
|
||||
# =============================================================================
|
||||
|
||||
def _run_win32(args: str, capture_output: bool, text: bool) -> CompletedProcess | t.CPtr:
|
||||
"""Windows 平台子进程执行(CreateProcessA + 管道)"""
|
||||
# 复制命令行(CreateProcessA 需要可修改的 lpCommandLine)
|
||||
args_len: t.CSizeT = len(args)
|
||||
cmd_line: bytes = _mbuddy.alloc(args_len + 1)
|
||||
if cmd_line == None:
|
||||
return CompletedProcess(args, -1, None, None)
|
||||
i: t.CSizeT = 0
|
||||
for ch in args:
|
||||
cmd_line[i] = ch
|
||||
i += 1
|
||||
cmd_line[args_len] = 0
|
||||
|
||||
# 安全属性(允许句柄继承)
|
||||
sa: w32.win32base.SECURITY_ATTRIBUTES = w32.win32base.SECURITY_ATTRIBUTES()
|
||||
sa.nLength = w32.win32base.SECURITY_ATTRIBUTES.__sizeof__()
|
||||
sa.lpSecurityDescriptor = t.CPtr(0)
|
||||
sa.bInheritHandle = 1 # TRUE
|
||||
|
||||
# 创建 stdout 管道
|
||||
stdout_read: w32.win32base.HANDLE = t.CPtr(0)
|
||||
stdout_write: w32.win32base.HANDLE = t.CPtr(0)
|
||||
if capture_output:
|
||||
pipe_result: BOOL = w32.win32file.CreatePipe(
|
||||
t.CPtr(c.Addr(stdout_read)),
|
||||
t.CPtr(c.Addr(stdout_write)),
|
||||
t.CPtr(c.Addr(sa)),
|
||||
0
|
||||
)
|
||||
if pipe_result == 0:
|
||||
return CompletedProcess(args, -1, None, None)
|
||||
# 防止读端被子进程继承
|
||||
w32.win32file.SetHandleInformation(stdout_read, w32.win32file.HANDLE_FLAG_INHERIT, 0)
|
||||
|
||||
# 设置启动信息
|
||||
si: w32.win32process.STARTUPINFOA = w32.win32process.STARTUPINFOA()
|
||||
si.cb = w32.win32process.STARTUPINFOA.__sizeof__()
|
||||
if capture_output:
|
||||
si.dwFlags = w32.win32process.STARTF_USESTDHANDLES
|
||||
si.hStdOutput = stdout_write
|
||||
si.hStdError = stdout_write # stderr 合并到 stdout
|
||||
|
||||
pi: w32.win32process.PROCESS_INFORMATION = w32.win32process.PROCESS_INFORMATION()
|
||||
|
||||
# 创建进程
|
||||
# capture_output=True 时用 CREATE_NO_WINDOW(输出到管道,不需要控制台)
|
||||
# capture_output=False 时用 0(继承父进程控制台,让 stdout 直接显示)
|
||||
create_flags: ULONG = w32.win32process.CREATE_NO_WINDOW if capture_output else 0
|
||||
create_result: BOOL = w32.win32process.CreateProcessA(
|
||||
t.CPtr(0), # lpApplicationName (NULL,从命令行解析)
|
||||
cmd_line, # lpCommandLine (可修改)
|
||||
t.CPtr(0), # lpProcessAttributes
|
||||
t.CPtr(0), # lpThreadAttributes
|
||||
1, # bInheritHandles = TRUE
|
||||
create_flags, # dwCreationFlags
|
||||
t.CPtr(0), # lpEnvironment (继承父进程)
|
||||
t.CPtr(0), # lpCurrentDirectory (继承父进程)
|
||||
t.CPtr(c.Addr(si)),
|
||||
t.CPtr(c.Addr(pi))
|
||||
)
|
||||
|
||||
if create_result == 0:
|
||||
# 创建失败
|
||||
if capture_output:
|
||||
w32.win32base.CloseHandle(stdout_read)
|
||||
w32.win32base.CloseHandle(stdout_write)
|
||||
return CompletedProcess(args, -1, None, None)
|
||||
|
||||
# 关闭父进程的写端(子进程已有写端的继承副本)
|
||||
if capture_output:
|
||||
w32.win32base.CloseHandle(stdout_write)
|
||||
|
||||
# 读取 stdout + 等待进程结束
|
||||
# 注意:必须边读边等,否则子进程输出超过管道缓冲区时会死锁
|
||||
# (子进程阻塞在写 stdout,父进程阻塞在 WaitForSingleObject)
|
||||
stdout_str: str = None
|
||||
if capture_output:
|
||||
buf_size: ULONG = OUTPUT_BUF_SIZE
|
||||
buf: bytes = _mbuddy.alloc(buf_size)
|
||||
if buf != None:
|
||||
total: ULONG = 0
|
||||
while 1:
|
||||
bytes_read: ULONG = 0
|
||||
read_result: BOOL = w32.win32file.ReadFile(
|
||||
stdout_read,
|
||||
buf + total,
|
||||
buf_size - 1 - total,
|
||||
t.CPtr(c.Addr(bytes_read)),
|
||||
t.CPtr(0)
|
||||
)
|
||||
if read_result == 0 or bytes_read == 0:
|
||||
break
|
||||
total += bytes_read
|
||||
# 缓冲区将满,扩容(翻倍)
|
||||
if total >= buf_size - 1 - 4096:
|
||||
new_size: ULONG = buf_size * 2
|
||||
new_buf: bytes = _mbuddy.alloc(new_size)
|
||||
if new_buf != None:
|
||||
string.memcpy(new_buf, buf, total)
|
||||
buf = new_buf
|
||||
buf_size = new_size
|
||||
else:
|
||||
# 扩容失败,停止读取
|
||||
break
|
||||
buf[total] = 0
|
||||
stdout_str = str(buf)
|
||||
|
||||
# 等待进程结束(如果还没退出的话)
|
||||
w32.win32sync.WaitForSingleObject(pi.hProcess, w32.win32base.INFINITE)
|
||||
|
||||
# 获取退出码
|
||||
exit_code: ULONG = 0
|
||||
w32.win32process.GetExitCodeProcess(pi.hProcess, t.CPtr(c.Addr(exit_code)))
|
||||
|
||||
# 关闭句柄
|
||||
w32.win32base.CloseHandle(pi.hProcess)
|
||||
w32.win32base.CloseHandle(pi.hThread)
|
||||
if capture_output:
|
||||
w32.win32base.CloseHandle(stdout_read)
|
||||
|
||||
return CompletedProcess(args, exit_code, stdout_str, None)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# POSIX 后端实现
|
||||
# =============================================================================
|
||||
|
||||
def _run_posix(args: str, capture_output: bool, text: bool) -> CompletedProcess | t.CPtr:
|
||||
"""POSIX 平台子进程执行(fork + execvp + pipe + waitpid)
|
||||
注意:当前为桩函数。POSIX 实现需要 pipe/fork/waitpid,
|
||||
这些函数在 Windows MinGW 上不可用,待 POSIX 平台测试时再完善。"""
|
||||
return CompletedProcess(args, -1, None, None)
|
||||
Reference in New Issue
Block a user