mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
- Add build-context helper to copy Docker context safely (ignore runtime data, keep symlinks) - Update UI and core actions to use ~/.prole/build and shared copy helper - Add/adjust tests and scripts; introduce knoe ops helpers and update manifests Co-authored-by: Junie <junie@jetbrains.com>
97 lines
2.5 KiB
Python
97 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
import errno
|
|
import os
|
|
import pty
|
|
import select
|
|
import subprocess
|
|
from collections.abc import Sequence
|
|
from typing import Callable
|
|
|
|
|
|
StreamCallback = Callable[[str], None]
|
|
|
|
|
|
def run_streaming_cmd(
|
|
cmd: str | Sequence[str],
|
|
cwd: str | None = None,
|
|
env: dict | None = None,
|
|
stdin_text: str | None = None,
|
|
on_stdout: StreamCallback | None = None,
|
|
on_stderr: StreamCallback | None = None,
|
|
) -> int:
|
|
"""Run a command with PTY-backed stdout/stderr and stream output chunks live."""
|
|
argv: list[str]
|
|
if isinstance(cmd, str):
|
|
argv = ["bash", "-lc", cmd]
|
|
else:
|
|
argv = list(cmd)
|
|
|
|
out_master, out_slave = pty.openpty()
|
|
err_master, err_slave = pty.openpty()
|
|
stdin_handle = subprocess.PIPE if stdin_text is not None else None
|
|
proc = None
|
|
|
|
try:
|
|
proc = subprocess.Popen(
|
|
argv,
|
|
cwd=cwd,
|
|
env=env,
|
|
stdin=stdin_handle,
|
|
stdout=out_slave,
|
|
stderr=err_slave,
|
|
text=False,
|
|
close_fds=True,
|
|
)
|
|
finally:
|
|
os.close(out_slave)
|
|
os.close(err_slave)
|
|
|
|
if stdin_text is not None and proc.stdin:
|
|
try:
|
|
proc.stdin.write(stdin_text.encode("utf-8", errors="replace"))
|
|
proc.stdin.close()
|
|
except Exception:
|
|
pass
|
|
|
|
callbacks: dict[int, StreamCallback | None] = {
|
|
out_master: on_stdout,
|
|
err_master: on_stderr,
|
|
}
|
|
|
|
try:
|
|
while callbacks:
|
|
ready, _, _ = select.select(list(callbacks.keys()), [], [], 0.1)
|
|
if not ready:
|
|
if proc.poll() is not None:
|
|
# Drain any remaining data after process exit.
|
|
ready = list(callbacks.keys())
|
|
else:
|
|
continue
|
|
|
|
for fd in ready:
|
|
try:
|
|
chunk = os.read(fd, 4096)
|
|
except OSError as exc:
|
|
if exc.errno == errno.EIO:
|
|
chunk = b""
|
|
else:
|
|
raise
|
|
|
|
if not chunk:
|
|
os.close(fd)
|
|
callbacks.pop(fd, None)
|
|
continue
|
|
|
|
callback = callbacks.get(fd)
|
|
if callback:
|
|
callback(chunk.decode("utf-8", errors="replace"))
|
|
|
|
return proc.wait()
|
|
finally:
|
|
for fd in list(callbacks.keys()):
|
|
try:
|
|
os.close(fd)
|
|
except OSError:
|
|
pass
|