Skip to content

Commit 5ec613c

Browse files
committed
Refactor Windows replacement for Popen to allow passing stdin to Popen process, and poll() and kill(). Fixes tests other.test_fix_closure and other.test_warn_undefined on Windows.
1 parent e20dbc1 commit 5ec613c

File tree

2 files changed

+52
-50
lines changed

2 files changed

+52
-50
lines changed

tests/runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7571,7 +7571,7 @@ def test_cmake(self):
75717571
if 'error' in ret[1].lower():
75727572
print >> sys.stderr, 'Failed command: ' + ' '.join(cmd)
75737573
print >> sys.stderr, 'Result:\n' + ret[1]
7574-
raise Exception('cmake call failed!') # cmake spams this silly message to stderr, so hide it: "Platform/Emscripten to use this system, please send your config file to [email protected] so it can be added to cmake"
7574+
raise Exception('cmake call failed!')
75757575
assert os.path.exists(tempdirname + '/Makefile'), 'CMake call did not produce a Makefile!'
75767576

75777577
# Build
@@ -8082,7 +8082,7 @@ def test_warn_undefined(self):
80828082
}
80838083
''')
80848084
output = Popen(['python', EMCC, os.path.join(self.get_dir(), 'main.cpp'), '-s', 'WARN_ON_UNDEFINED_SYMBOLS=1'], stderr=PIPE).communicate()
8085-
self.assertContained('Unresolved symbol: _something\n', output[1])
8085+
self.assertContained('Unresolved symbol: _something', output[1])
80868086

80878087
output = Popen(['python', EMCC, os.path.join(self.get_dir(), 'main.cpp')], stderr=PIPE).communicate()
80888088
self.assertNotContained('Unresolved symbol: _something\n', output[1])

tools/shared.py

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,63 @@
22
from subprocess import Popen, PIPE, STDOUT
33
from tempfile import mkstemp
44

5-
# A helper class that is used on Windows to represent the subprocess object that is the return value of Popen.
6-
class MockPopen:
7-
def __init__(self, stdout, stderr, returncode):
8-
self.returncode = returncode
9-
self.output = (stdout, stderr)
10-
def communicate(self):
11-
return self.output
12-
def poll(self):
13-
return self.returncode
14-
def kill(self):
15-
return # We've already communicate()d the process (waited for its completion), so can't kill() anymore. Therefore this is a no-op.
16-
175
# On Windows python suffers from a particularly nasty bug if python is spawning new processes while python itself is spawned from some other non-console process.
186
# Use a custom replacement for Popen on Windows to avoid the "WindowsError: [Error 6] The handle is invalid" errors when emcc is driven through cmake or mingw32-make.
197
# See http://bugs.python.org/issue3905
20-
def call_process(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False,
21-
shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0):
22-
# (stdin, stdout, stderr) store what the caller originally wanted to be done with the streams.
23-
# (stdin_, stdout_, stderr_) will store the fixed set of streams that workaround the bug.
24-
stdin_ = stdin
25-
stdout_ = stdout
26-
stderr_ = stderr
27-
28-
# If the caller wants one of these PIPEd, we must PIPE them all to avoid the 'handle is invalid' bug.
29-
if stdin_ == PIPE or stdout_ == PIPE or stderr_ == PIPE:
30-
if stdin_ == None:
31-
stdin_ = PIPE
32-
if stdout_ == None:
33-
stdout_ = PIPE
34-
if stderr_ == None:
35-
stderr_ = PIPE
36-
37-
# Call the process with fixed streams.
38-
process = subprocess.Popen(args, bufsize, executable, stdin_, stdout_, stderr_, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags)
39-
output = process.communicate()
40-
41-
# If caller never wanted to PIPE stdout or stderr, route the output back to screen to avoid swallowing output.
42-
if stdout == None and stdout_ == PIPE and len(output[0].strip()) > 0:
43-
print >> sys.stdout, output[0]
44-
if stderr == None and stderr_ == PIPE and len(output[1].strip()) > 0:
45-
print >> sys.stderr, output[1]
8+
class WindowsPopen:
9+
def __init__(self, args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False,
10+
shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0):
11+
self.stdin = stdin
12+
self.stdout = stdout
13+
self.stderr = stderr
14+
15+
# (stdin, stdout, stderr) store what the caller originally wanted to be done with the streams.
16+
# (stdin_, stdout_, stderr_) will store the fixed set of streams that workaround the bug.
17+
self.stdin_ = stdin
18+
self.stdout_ = stdout
19+
self.stderr_ = stderr
20+
21+
# If the caller wants one of these PIPEd, we must PIPE them all to avoid the 'handle is invalid' bug.
22+
if self.stdin_ == PIPE or self.stdout_ == PIPE or self.stderr_ == PIPE:
23+
if self.stdin_ == None:
24+
self.stdin_ = PIPE
25+
if self.stdout_ == None:
26+
self.stdout_ = PIPE
27+
if self.stderr_ == None:
28+
self.stderr_ = PIPE
4629

47-
# Return a mock object to the caller. This works as long as all emscripten code immediately .communicate()s the result, and doesn't
48-
# leave the process object around for longer/more exotic uses.
49-
if stdout == None and stderr == None:
50-
return MockPopen(None, None, process.returncode)
51-
if stdout == None:
52-
return MockPopen(None, output[1], process.returncode)
53-
if stderr == None:
54-
return MockPopen(output[0], None, process.returncode)
55-
return MockPopen(output[0], output[1], process.returncode)
30+
# Call the process with fixed streams.
31+
self.process = subprocess.Popen(args, bufsize, executable, self.stdin_, self.stdout_, self.stderr_, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags)
32+
33+
def communicate(self, input=None):
34+
output = self.process.communicate(input)
35+
self.returncode = self.process.returncode
36+
37+
# If caller never wanted to PIPE stdout or stderr, route the output back to screen to avoid swallowing output.
38+
if self.stdout == None and self.stdout_ == PIPE and len(output[0].strip()) > 0:
39+
print >> sys.stdout, output[0]
40+
if self.stderr == None and self.stderr_ == PIPE and len(output[1].strip()) > 0:
41+
print >> sys.stderr, output[1]
42+
43+
# Return a mock object to the caller. This works as long as all emscripten code immediately .communicate()s the result, and doesn't
44+
# leave the process object around for longer/more exotic uses.
45+
if self.stdout == None and self.stderr == None:
46+
return (None, None)
47+
if self.stdout == None:
48+
return (None, output[1])
49+
if self.stderr == None:
50+
return (output[0], None)
51+
return (output[0], output[1])
52+
53+
def poll(self):
54+
return self.process.returncode
55+
56+
def kill(self):
57+
return self.process.kill
5658

5759
# Install our replacement Popen handler if we are running on Windows to avoid python spawn process function.
5860
if os.name == 'nt':
59-
Popen = call_process
61+
Popen = WindowsPopen
6062

6163
import js_optimizer
6264

0 commit comments

Comments
 (0)