mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 00:14:41 +01:00
Refreshing procs/specs.py
(#5424)
### Motivation This PR just clean the code in `procs/specs.py` without changes the logic: group the code into functions. This is needed to the next feature PR #5443. ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
14342b2ea3
commit
2d966fb1b5
2 changed files with 135 additions and 80 deletions
23
news/refresh_specs.rst
Normal file
23
news/refresh_specs.rst
Normal file
|
@ -0,0 +1,23 @@
|
|||
**Added:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Changed:**
|
||||
|
||||
* Cleaning logic and functions around threading and capturing in ``procs/specs.py`` file.
|
||||
|
||||
**Deprecated:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Removed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Fixed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Security:**
|
||||
|
||||
* <news item>
|
|
@ -374,6 +374,7 @@ class SubprocSpec:
|
|||
self.is_proxy = False
|
||||
self.background = False
|
||||
self.threadable = True
|
||||
self.force_threadable = None # Set this value to ignore threadable prediction.
|
||||
self.pipeline_index = None
|
||||
self.last_in_pipeline = False
|
||||
self.captured_stdout = None
|
||||
|
@ -600,7 +601,6 @@ class SubprocSpec:
|
|||
spec = kls(cmd, cls=cls, **kwargs)
|
||||
# modifications that alter cmds must come after creating instance
|
||||
spec.resolve_args_list()
|
||||
# perform initial redirects
|
||||
spec.resolve_redirects()
|
||||
spec.resolve_alias()
|
||||
spec.resolve_binary_loc()
|
||||
|
@ -711,15 +711,10 @@ class SubprocSpec:
|
|||
|
||||
def resolve_alias_cls(self):
|
||||
"""Determine which proxy class to run an alias with."""
|
||||
alias = self.alias
|
||||
if not callable(alias):
|
||||
return
|
||||
self.is_proxy = True
|
||||
self.threadable = XSH.env.get("THREAD_SUBPROCS") and getattr(
|
||||
alias, "__xonsh_threadable__", True
|
||||
)
|
||||
self.cls = ProcProxyThread if self.threadable else ProcProxy
|
||||
self.captured = getattr(alias, "__xonsh_capturable__", self.captured)
|
||||
if callable(self.alias):
|
||||
self.is_proxy = True
|
||||
_update_proc_alias_threadable(self)
|
||||
_update_proc_alias_captured(self)
|
||||
|
||||
def resolve_stack(self):
|
||||
"""Computes the stack for a callable alias's call-site, if needed."""
|
||||
|
@ -765,32 +760,55 @@ def _safe_pipe_properties(fd, use_tty=False):
|
|||
|
||||
|
||||
def _update_last_spec(last):
|
||||
env = XSH.env
|
||||
captured = last.captured
|
||||
last.last_in_pipeline = True
|
||||
if not captured:
|
||||
|
||||
if not last.captured:
|
||||
return
|
||||
callable_alias = callable(last.alias)
|
||||
if callable_alias:
|
||||
if last.cls is ProcProxy and captured == "hiddenobject":
|
||||
# a ProcProxy run using ![] should not be captured
|
||||
return
|
||||
else:
|
||||
cmds_cache = XSH.commands_cache
|
||||
thable = (
|
||||
env.get("THREAD_SUBPROCS")
|
||||
and (captured != "hiddenobject" or env.get("XONSH_CAPTURE_ALWAYS"))
|
||||
and cmds_cache.predict_threadable(last.args)
|
||||
and cmds_cache.predict_threadable(last.cmd)
|
||||
)
|
||||
if captured and thable:
|
||||
|
||||
_last_spec_update_threading(last)
|
||||
_last_spec_update_captured(last)
|
||||
|
||||
|
||||
def _last_spec_update_threading(last: SubprocSpec):
|
||||
if callable(last.alias):
|
||||
return
|
||||
|
||||
captured, env, cmds_cache = last.captured, XSH.env, XSH.commands_cache
|
||||
threadable = (
|
||||
captured
|
||||
and env.get("THREAD_SUBPROCS")
|
||||
and (captured != "hiddenobject" or env.get("XONSH_CAPTURE_ALWAYS"))
|
||||
and cmds_cache.predict_threadable(last.args)
|
||||
and cmds_cache.predict_threadable(last.cmd)
|
||||
)
|
||||
if last.force_threadable is not None:
|
||||
threadable = last.force_threadable
|
||||
|
||||
if threadable:
|
||||
if last.captured:
|
||||
last.cls = PopenThread
|
||||
elif not thable:
|
||||
# foreground processes should use Popen
|
||||
last.threadable = False
|
||||
if captured == "object" or captured == "hiddenobject":
|
||||
# CommandPipeline objects should not pipe stdout, stderr
|
||||
return
|
||||
else:
|
||||
last.threadable = False
|
||||
|
||||
|
||||
def _last_spec_update_captured(last: SubprocSpec):
|
||||
captured = (
|
||||
(captured := last.captured)
|
||||
and not (captured in ["object", "hiddenobject"] and not last.threadable)
|
||||
# a ProcProxy run using ![] should not be captured
|
||||
and not (
|
||||
callable(last.alias)
|
||||
and last.cls is ProcProxy
|
||||
and captured == "hiddenobject"
|
||||
)
|
||||
)
|
||||
if captured:
|
||||
_make_last_spec_captured(last)
|
||||
|
||||
|
||||
def _make_last_spec_captured(last: SubprocSpec):
|
||||
captured = last.captured
|
||||
callable_alias = callable(last.alias)
|
||||
# cannot used PTY pipes for aliases, for some dark reason,
|
||||
# and must use normal pipes instead.
|
||||
use_tty = xp.ON_POSIX and not callable_alias
|
||||
|
@ -849,6 +867,46 @@ def _update_last_spec(last):
|
|||
last.captured_stderr = last.captured_stdout
|
||||
|
||||
|
||||
def _update_proc_alias_threadable(proc):
|
||||
threadable = XSH.env.get("THREAD_SUBPROCS") and getattr(
|
||||
proc.alias, "__xonsh_threadable__", True
|
||||
)
|
||||
if proc.force_threadable is not None:
|
||||
threadable = proc.force_threadable
|
||||
proc.threadable = threadable
|
||||
proc.cls = ProcProxyThread if proc.threadable else ProcProxy
|
||||
|
||||
|
||||
def _update_proc_alias_captured(proc):
|
||||
proc.captured = getattr(proc.alias, "__xonsh_capturable__", proc.captured)
|
||||
|
||||
|
||||
def _trace_specs(trace_mode, specs, cmds, captured):
|
||||
"""Show information about specs."""
|
||||
tracer = XSH.env.get("XONSH_TRACE_SUBPROC_FUNC", None)
|
||||
if callable(tracer):
|
||||
tracer(cmds, captured=captured)
|
||||
else:
|
||||
r = {"cmds": cmds, "captured": captured}
|
||||
print(f"Trace run_subproc({repr(r)})", file=sys.stderr)
|
||||
if trace_mode == 2:
|
||||
for i, s in enumerate(specs):
|
||||
pcls = s.cls.__module__ + "." + s.cls.__name__
|
||||
pcmd = (
|
||||
[s.args[0].__name__] + s.args[1:] if callable(s.args[0]) else s.args
|
||||
)
|
||||
p = {
|
||||
"cmd": pcmd,
|
||||
"cls": pcls,
|
||||
"alias": s.alias_name,
|
||||
"bin": s.binary_loc,
|
||||
"threadable": s.threadable,
|
||||
"bg": s.background,
|
||||
}
|
||||
p = {k: v for k, v in p.items() if v is not None}
|
||||
print(f"{i}: {repr(p)}", file=sys.stderr)
|
||||
|
||||
|
||||
def cmds_to_specs(cmds, captured=False, envs=None):
|
||||
"""Converts a list of cmds to a list of SubprocSpec objects that are
|
||||
ready to be executed.
|
||||
|
@ -894,8 +952,17 @@ def cmds_to_specs(cmds, captured=False, envs=None):
|
|||
return specs
|
||||
|
||||
|
||||
def _should_set_title():
|
||||
return XSH.env.get("XONSH_INTERACTIVE") and XSH.shell is not None
|
||||
def _shell_set_title(cmds):
|
||||
if XSH.env.get("XONSH_INTERACTIVE") and XSH.shell is not None:
|
||||
# context manager updates the command information that gets
|
||||
# accessed by CurrentJobField when setting the terminal's title
|
||||
with XSH.env["PROMPT_FIELDS"]["current_job"].update_current_cmds(cmds):
|
||||
# remove current_job from prompt level cache
|
||||
XSH.env["PROMPT_FIELDS"].reset_key("current_job")
|
||||
# The terminal's title needs to be set before starting the
|
||||
# subprocess to avoid accidentally answering interactive questions
|
||||
# from commands such as `rm -i` (see #1436)
|
||||
XSH.shell.settitle()
|
||||
|
||||
|
||||
def run_subproc(cmds, captured=False, envs=None):
|
||||
|
@ -914,49 +981,14 @@ def run_subproc(cmds, captured=False, envs=None):
|
|||
|
||||
specs = cmds_to_specs(cmds, captured=captured, envs=envs)
|
||||
|
||||
if tr := XSH.env.get("XONSH_TRACE_SUBPROC", False):
|
||||
tracer = XSH.env.get("XONSH_TRACE_SUBPROC_FUNC", None)
|
||||
if callable(tracer):
|
||||
tracer(cmds, captured=captured)
|
||||
else:
|
||||
r = {"cmds": cmds, "captured": captured}
|
||||
print(f"Trace run_subproc({repr(r)})", file=sys.stderr)
|
||||
if tr == 2:
|
||||
for i, s in enumerate(specs):
|
||||
pcls = s.cls.__module__ + "." + s.cls.__name__
|
||||
pcmd = (
|
||||
[s.args[0].__name__] + s.args[1:]
|
||||
if callable(s.args[0])
|
||||
else s.args
|
||||
)
|
||||
p = {
|
||||
"cmd": pcmd,
|
||||
"cls": pcls,
|
||||
"alias": s.alias_name,
|
||||
"bin": s.binary_loc,
|
||||
"thread": s.threadable,
|
||||
"bg": s.background,
|
||||
}
|
||||
p = {k: v for k, v in p.items() if v is not None}
|
||||
print(f"{i}: {repr(p)}", file=sys.stderr)
|
||||
if trace_mode := XSH.env.get("XONSH_TRACE_SUBPROC", False):
|
||||
_trace_specs(trace_mode, specs, cmds, captured)
|
||||
|
||||
cmds = [
|
||||
_flatten_cmd_redirects(cmd) if isinstance(cmd, list) else cmd for cmd in cmds
|
||||
]
|
||||
if _should_set_title():
|
||||
# context manager updates the command information that gets
|
||||
# accessed by CurrentJobField when setting the terminal's title
|
||||
with XSH.env["PROMPT_FIELDS"]["current_job"].update_current_cmds(cmds):
|
||||
# remove current_job from prompt level cache
|
||||
XSH.env["PROMPT_FIELDS"].reset_key("current_job")
|
||||
# The terminal's title needs to be set before starting the
|
||||
# subprocess to avoid accidentally answering interactive questions
|
||||
# from commands such as `rm -i` (see #1436)
|
||||
XSH.shell.settitle()
|
||||
# run the subprocess
|
||||
return _run_specs(specs, cmds)
|
||||
else:
|
||||
return _run_specs(specs, cmds)
|
||||
_shell_set_title(cmds)
|
||||
return _run_specs(specs, cmds)
|
||||
|
||||
|
||||
def _run_command_pipeline(specs, cmds):
|
||||
|
@ -991,15 +1023,15 @@ def _run_specs(specs, cmds):
|
|||
cp.spec.background,
|
||||
)
|
||||
|
||||
# For some reason, some programs are in a stopped state when the flow
|
||||
# reaches this point, hence a SIGCONT should be sent to `proc` to make
|
||||
# sure that the shell doesn't hang.
|
||||
# See issue #2999 and the fix in PR #3000
|
||||
"""
|
||||
For some reason, some programs are in a stopped state when the flow
|
||||
reaches this point, hence a SIGCONT should be sent to `proc` to make
|
||||
sure that the shell doesn't hang. See issue #2999 and the fix in PR #3000
|
||||
"""
|
||||
resume_process(proc)
|
||||
|
||||
# now figure out what we should return
|
||||
if captured == "object":
|
||||
return cp # object can be returned even if backgrounding
|
||||
return cp
|
||||
elif captured == "hiddenobject":
|
||||
if not background:
|
||||
cp.end()
|
||||
|
|
Loading…
Add table
Reference in a new issue