New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hooks: Runtime hook for subprocess block launching standalone cmd #7118
Comments
I presume you are building with Forcing the unused pipes to DEVNULL is required to solve a different class of problems within noconsole frozen applications, so it is going to stay. I think the only way to also accommodate this use case is to exempt attempts to spawn |
Does the example actually work if ran via |
It does, and it opens a new console window. If ran through regular |
Hmm, surely that would mean that |
It does the same with If you provide this flags |
I had some time to dig around this problem, and I think the error with stream handles that the Test application
import sys
import os
import signal
import shlex
import subprocess
from PySide2 import QtCore, QtWidgets, QtGui
# Identify application type
if getattr(sys, 'frozen', False):
if os.path.dirname(__file__) == os.path.dirname(sys.executable):
APP_TYPE = "onedir"
else:
APP_TYPE = "onefile"
if not isinstance(sys.stdout, io.IOBase):
APP_TYPE += ", windowed"
else:
APP_TYPE += ", console"
else:
APP_TYPE = 'unfrozen'
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.process = None
self.processStatusTimer = QtCore.QTimer()
self.processStatusTimer.setTimerType(QtCore.Qt.PreciseTimer)
self.processStatusTimer.setInterval(100)
self.processStatusTimer.timeout.connect(self._check_subprocess_status)
# *** UI ***
self.setWindowTitle(f"Subprocess test ({APP_TYPE})")
layout = QtWidgets.QFormLayout(self)
layout.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
# Handles info
self.labelHandleInfo= QtWidgets.QLabel("<b>Handle info:</b>")
layout.addRow(self.labelHandleInfo)
self.textEditHandleInfo= QtWidgets.QTextEdit()
layout.addRow(self.textEditHandleInfo)
# Command line
self.lineEditCommandLine = QtWidgets.QLineEdit()
layout.addRow("Command line: ", self.lineEditCommandLine)
# Redirect streams
self.checkBoxCloseStdin = QtWidgets.QCheckBox("Close stdin")
layout.addRow(self.checkBoxCloseStdin)
self.checkBoxCaptureStdout= QtWidgets.QCheckBox("Capture stdout")
layout.addRow(self.checkBoxCaptureStdout)
self.checkBoxCaptureStderr= QtWidgets.QCheckBox("Capture stderr")
layout.addRow(self.checkBoxCaptureStderr)
# Run
self.buttonRun = QtWidgets.QPushButton("Run")
layout.addRow(self.buttonRun)
self.buttonRun.clicked.connect(self._run_subprocess)
# Output
self.labelOutput = QtWidgets.QLabel("<b>Output:</b>")
layout.addRow(self.labelOutput)
self.textEditOutput = QtWidgets.QTextEdit()
layout.addRow(self.textEditOutput)
# Populate handle info text box
self.textEditHandleInfo.setText(self._get_handle_info())
def _get_handle_info(self):
text = ''
import traceback
# Use the same _winapi module that subprocess uses
try:
import _winapi
except Exception as e:
return f"Failed to query handle info: Failed to import _winapi: {e}"
text += "STDIN:\n"
try:
handle = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE)
text += f"Handle: {handle!r}"
except Exception as e:
tb = traceback.format_exc()
text += f"Failed to query stdin: {e}\n{tb}"
text += "\n"
text += "STDOUT:\n"
try:
handle = _winapi.GetStdHandle(_winapi.STD_OUTPUT_HANDLE)
text += f"Handle: {handle!r}"
except Exception as e:
tb = traceback.format_exc()
text += f"Failed to query stdout: {e}\n{tb}"
text += "\n"
text += "STDERR:\n"
try:
handle = _winapi.GetStdHandle(_winapi.STD_ERROR_HANDLE)
text += f"Handle: {handle!r}"
except Exception as e:
tb = traceback.format_exc()
text += f"Failed to query stderr: {e}\n{tb}"
text += "\n"
return text
def _run_subprocess(self):
command = self.lineEditCommandLine.text()
command = shlex.split(command)
print(f"Running command: {command!r}")
self.textEditOutput.setText("")
kwargs = {}
if self.checkBoxCloseStdin.isChecked():
kwargs["stdin"] = subprocess.DEVNULL
if self.checkBoxCaptureStdout.isChecked():
kwargs["stdout"] = subprocess.PIPE
if self.checkBoxCaptureStdout.isChecked():
kwargs["stderr"] = subprocess.PIPE
try:
self.process = subprocess.Popen(command, **kwargs)
except Exception as e:
import traceback
tb = traceback.format_exc()
QtWidgets.QMessageBox.critical(self, "Error", f"Failed to spawn process {command!r}:\n{e}\nTB:\n{tb}")
return
self.processStatusTimer.start()
def _check_subprocess_status(self):
running = False
if self.process:
self.process.poll()
if self.process.returncode is None:
# Still running
running = True
else:
print(f"Process finished! Object: {self.process!r}")
self.processStatusTimer.stop()
text = (
f"Return code: {self.process.returncode}\n\n"
f"Stdout:\n{self.process.stdout.read().decode('utf-8') if self.process.stdout else 'N/A'}\n\n"
f"Stderr:\n{self.process.stderr.read().decode('utf-8') if self.process.stderr else 'N/A'}\n\n"
)
self.textEditOutput.setText(text)
self.process = None
if running:
self.buttonRun.setText("Running")
self.buttonRun.setEnabled(False)
else:
self.buttonRun.setText("Run")
self.buttonRun.setEnabled(True)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
signal.signal(signal.SIGINT, signal.SIG_DFL)
main = MainWindow()
main.show()
sys.exit(app.exec_()) The test program allows spawning a sub-process using the command-line entered in the text field, and allows optionally closing stdin ( The test application was ran:
The test command was Unfrozen variant always worked, regardless the stream closing/redirection settings. The The Looking at That's why the final iteration of the test application also tries running Once we know that the problem is specific to pyinstaller/bootloader/src/pyi_utils.c Lines 842 to 844 in 7b39785
In Then, we can remove the runtime hook, and interfering with stream handles will also solve this problem with interactive shells that require stdin. |
Nice. I can't say that I'll miss that runtime hook. |
Description of the issue
In windowed build using
PySide2
,subprocess.Popen(["cmd"])
no longer work (cmd open then close immediately). I figured out that the issue come from the subprocess hook (since v4.8, pr #6364). If I comment out this file,cmd
start showing again and stay alive.Context information (for bug reports)
pyinstaller --version
:5.4.1
A minimal example program which shows the error
A cmd shows up at start, if you comment the hook it stays alive, if you don't the cmd disappear instantly.
The text was updated successfully, but these errors were encountered: