##// END OF EJS Templates
Short in the dark windows issue
M Bussonnier -
Show More
@@ -18,14 +18,14 import subprocess
18 import shlex
18 import shlex
19 import sys
19 import sys
20 import os
20 import os
21
21 from typing import Callable, Optional, Union, List
22 from IPython.utils import py3compat
22 from IPython.utils import py3compat
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Function definitions
25 # Function definitions
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 def read_no_interrupt(p):
28 def read_no_interrupt(p: subprocess.Popen):
29 """Read from a pipe ignoring EINTR errors.
29 """Read from a pipe ignoring EINTR errors.
30
30
31 This is necessary because when reading from pipes with GUI event loops
31 This is necessary because when reading from pipes with GUI event loops
@@ -40,7 +40,7 def read_no_interrupt(p):
40 raise
40 raise
41
41
42
42
43 def process_handler(cmd, callback, stderr=subprocess.PIPE):
43 def process_handler(cmd:Union[str, List[str]], callback:Callable[[subprocess.Popen], int], stderr=subprocess.PIPE) -> Optional[int]:
44 """Open a command in a shell subprocess and execute a callback.
44 """Open a command in a shell subprocess and execute a callback.
45
45
46 This function provides common scaffolding for creating subprocess.Popen()
46 This function provides common scaffolding for creating subprocess.Popen()
@@ -67,7 +67,10 def process_handler(cmd, callback, stderr=subprocess.PIPE):
67 sys.stdout.flush()
67 sys.stdout.flush()
68 sys.stderr.flush()
68 sys.stderr.flush()
69 # On win32, close_fds can't be true when using pipes for stdin/out/err
69 # On win32, close_fds can't be true when using pipes for stdin/out/err
70 close_fds = sys.platform != 'win32'
70 if sys.platform == "win32" and stderr != subprocess.PIPE:
71 close_fds = False
72 else:
73 close_fds = True
71 # Determine if cmd should be run with system shell.
74 # Determine if cmd should be run with system shell.
72 shell = isinstance(cmd, str)
75 shell = isinstance(cmd, str)
73 # On POSIX systems run shell commands with user-preferred shell.
76 # On POSIX systems run shell commands with user-preferred shell.
@@ -24,6 +24,7 from ctypes import c_int, POINTER
24 from ctypes.wintypes import LPCWSTR, HLOCAL
24 from ctypes.wintypes import LPCWSTR, HLOCAL
25 from subprocess import STDOUT, TimeoutExpired
25 from subprocess import STDOUT, TimeoutExpired
26 from threading import Thread
26 from threading import Thread
27 import subprocess
27
28
28 # our own imports
29 # our own imports
29 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
30 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
@@ -34,7 +35,7 from .encoding import DEFAULT_ENCODING
34 # Function definitions
35 # Function definitions
35 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
36
37
37 class AvoidUNCPath(object):
38 class AvoidUNCPath:
38 """A context manager to protect command execution from UNC paths.
39 """A context manager to protect command execution from UNC paths.
39
40
40 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
41 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
@@ -71,35 +72,50 class AvoidUNCPath(object):
71 os.chdir(self.path)
72 os.chdir(self.path)
72
73
73
74
74 def _system_body(p):
75 def _system_body(p: subprocess.Popen) -> int:
75 """Callback for _system."""
76 """Callback for _system."""
76 enc = DEFAULT_ENCODING
77 enc = DEFAULT_ENCODING
77
78
78 def stdout_read():
79 def stdout_read():
80 try:
79 for line in read_no_interrupt(p.stdout).splitlines():
81 for line in read_no_interrupt(p.stdout).splitlines():
80 line = line.decode(enc, 'replace')
82 line = line.decode(enc, 'replace')
81 print(line, file=sys.stdout)
83 print(line, file=sys.stdout)
84 except Exception as e:
85 print(f"Error reading stdout: {e}", file=sys.stderr)
82
86
83 def stderr_read():
87 def stderr_read():
88 try:
84 for line in read_no_interrupt(p.stderr).splitlines():
89 for line in read_no_interrupt(p.stderr).splitlines():
85 line = line.decode(enc, 'replace')
90 line = line.decode(enc, 'replace')
86 print(line, file=sys.stderr)
91 print(line, file=sys.stderr)
92 except Exception as e:
93 print(f"Error reading stderr: {e}", file=sys.stderr)
94
95 stdout_thread = Thread(target=stdout_read)
96 stderr_thread = Thread(target=stderr_read)
87
97
88 Thread(target=stdout_read).start()
98 stdout_thread.start()
89 Thread(target=stderr_read).start()
99 stderr_thread.start()
90
100
91 # Wait to finish for returncode. Unfortunately, Python has a bug where
101 # Wait to finish for returncode. Unfortunately, Python has a bug where
92 # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in
102 # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in
93 # a loop instead of just doing `return p.wait()`.
103 # a loop instead of just doing `return p.wait()`
94 while True:
104 while True:
95 result = p.poll()
105 result = p.poll()
96 if result is None:
106 if result is None:
97 time.sleep(0.01)
107 time.sleep(0.01)
98 else:
108 else:
109 break
110
111 # Join the threads to ensure they complete before returning
112 stdout_thread.join()
113 stderr_thread.join()
114
99 return result
115 return result
100
116
101
117
102 def system(cmd):
118 def system(cmd: str):
103 """Win32 version of os.system() that works with network shares.
119 """Win32 version of os.system() that works with network shares.
104
120
105 Note that this implementation returns None, as meant for use in IPython.
121 Note that this implementation returns None, as meant for use in IPython.
General Comments 0
You need to be logged in to leave comments. Login now