Files
Red-DiscordBot/redbot/_update/runner.py
T
2026-05-13 14:14:43 -08:00

120 lines
3.4 KiB
Python

import enum
import dataclasses
import json
import os
import sys
from pathlib import Path
from typing import Any, ClassVar, Dict, Iterable, NoReturn, Optional, Tuple, Union
from . import cmd, common
_RUNNER_DIR = Path(os.environ.get(common.RUNNER_DIR_ENV_VAR, ""))
class RequestType(enum.Enum):
exec = "exec"
spawn_command = "spawn_command"
@dataclasses.dataclass(frozen=True)
class RequestInput:
request_type: ClassVar[RequestType]
request_new_python_exe: str
request_new_start_args: Tuple[str, ...]
request_set_env_vars: Dict[str, Optional[str]]
@dataclasses.dataclass(frozen=True)
class RequestOutput:
request_type: RequestType
@dataclasses.dataclass(frozen=True)
class ExecRequestInput(RequestInput):
request_type: ClassVar = RequestType.exec
@dataclasses.dataclass(frozen=True)
class ExecRequestOutput(RequestOutput):
pass
@dataclasses.dataclass(frozen=True)
class SpawnProcessRequestInput(RequestInput):
request_type: ClassVar = RequestType.spawn_command
command: str
args: Tuple[str, ...]
env: Optional[Dict[str, str]]
@dataclasses.dataclass(frozen=True)
class SpawnProcessRequestOutput(RequestOutput):
exit_code: int
exited: bool
pid: int
sys: Any
sys_usage: Dict[str, Any]
system_time: int
user_time: int
def make_request(request: RequestInput) -> NoReturn:
with open(_RUNNER_DIR / "request_input.json", "w", encoding="utf-8") as fp:
data = dataclasses.asdict(request)
data["request_type"] = request.request_type.value
json.dump(data, fp)
raise SystemExit(3)
def get_request_output() -> Union[ExecRequestOutput, SpawnProcessRequestOutput]:
with open(_RUNNER_DIR / "request_output.json", encoding="utf-8") as fp:
data = json.load(fp)
request_type = RequestType(data.pop("request_type"))
if request_type == RequestType.exec:
return ExecRequestOutput(request_type=request_type)
elif request_type == RequestType.spawn_command:
return SpawnProcessRequestOutput(request_type=request_type, **data)
raise RuntimeError("unreachable code")
def make_spawn_process_request(
command: str,
*args: str,
env: Optional[Dict[str, str]] = None,
new_start_args: Iterable[str],
new_python_exe: str = sys.executable,
set_env_vars: Optional[Dict[str, Optional[str]]] = None,
) -> NoReturn:
if set_env_vars is None:
set_env_vars = {}
debug_args = (cmd.arg_names.DEBUG,) * common.get_log_cli_level()
request = SpawnProcessRequestInput(
request_new_python_exe=new_python_exe,
request_new_start_args=("-m", "redbot._update.internal", *debug_args, *new_start_args),
request_set_env_vars=set_env_vars,
command=command,
args=args,
env=env,
)
make_request(request)
def make_exec_request(
new_python_exe: str,
*new_start_args: str,
set_env_vars: Optional[Dict[str, Optional[str]]] = None,
) -> NoReturn:
if set_env_vars is None:
set_env_vars = {}
debug_args = (cmd.arg_names.DEBUG,) * common.get_log_cli_level()
request = ExecRequestInput(
request_new_python_exe=new_python_exe,
request_new_start_args=("-m", "redbot._update.internal", *debug_args, *new_start_args),
request_set_env_vars=set_env_vars,
)
make_request(request)
def get_wrapper_executable() -> Path:
return Path(os.environ[common.RUNNER_WRAPPER_EXE_ENV_VAR])