src/eric7/VirtualEnv/VirtualenvExecDialog.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9016
6f079c524e99
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the virtualenv execution dialog.
8 """
9
10 import os
11
12 from PyQt6.QtCore import QProcess, QTimer, QUrl
13 from PyQt6.QtGui import QDesktopServices
14 from PyQt6.QtWidgets import QDialog, QDialogButtonBox
15
16 from .Ui_VirtualenvExecDialog import Ui_VirtualenvExecDialog
17
18 import Preferences
19 from Globals import isWindowsPlatform, getPythonExecutable
20
21
22 class VirtualenvExecDialog(QDialog, Ui_VirtualenvExecDialog):
23 """
24 Class implementing the virtualenv execution dialog.
25
26 This class starts a QProcess and displays a dialog that
27 shows the output of the virtualenv or pyvenv process.
28 """
29 def __init__(self, configuration, venvManager, parent=None):
30 """
31 Constructor
32
33 @param configuration dictionary containing the configuration parameters
34 as returned by the command configuration dialog
35 @type dict
36 @param venvManager reference to the virtual environment manager
37 @type VirtualenvManager
38 @param parent reference to the parent widget
39 @type QWidget
40 """
41 super().__init__(parent)
42 self.setupUi(self)
43
44 self.buttonBox.button(
45 QDialogButtonBox.StandardButton.Close).setEnabled(False)
46 self.buttonBox.button(
47 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
48
49 self.__pyvenv = configuration["envType"] == "pyvenv"
50 self.__targetDir = configuration["targetDirectory"]
51 self.__openTarget = configuration["openTarget"]
52 self.__createLog = configuration["createLog"]
53 self.__createScript = configuration["createScript"]
54 self.__venvName = configuration["logicalName"]
55 self.__venvManager = venvManager
56
57 self.__process = None
58 self.__cmd = ""
59
60 if self.__pyvenv:
61 self.__calls = []
62 if configuration["pythonExe"]:
63 self.__calls.append((configuration["pythonExe"],
64 ["-m", "venv"]))
65 self.__calls.extend([
66 (getPythonExecutable(), ["-m", "venv"]),
67 ("python3", ["-m", "venv"]),
68 ("python", ["-m", "venv"]),
69 ])
70 else:
71 self.__calls = [
72 (getPythonExecutable(), ["-m", "virtualenv"]),
73 ("virtualenv", []),
74 ]
75 self.__callIndex = 0
76 self.__callArgs = []
77
78 def start(self, arguments):
79 """
80 Public slot to start the virtualenv command.
81
82 @param arguments commandline arguments for virtualenv/pyvenv program
83 (list of strings)
84 """
85 if self.__callIndex == 0:
86 # first attempt, add a given python interpreter and do
87 # some other setup
88 self.errorGroup.hide()
89 self.contents.clear()
90 self.errors.clear()
91
92 self.__process = QProcess()
93 self.__process.readyReadStandardOutput.connect(self.__readStdout)
94 self.__process.readyReadStandardError.connect(self.__readStderr)
95 self.__process.finished.connect(self.__finish)
96
97 if not self.__pyvenv:
98 for arg in arguments:
99 if arg.startswith("--python="):
100 prog = arg.replace("--python=", "")
101 self.__calls.insert(
102 0, (prog, ["-m", "virtualenv"]))
103 break
104 self.__callArgs = arguments
105
106 prog, args = self.__calls[self.__callIndex]
107 args.extend(self.__callArgs)
108 self.__cmd = "{0} {1}".format(prog, " ".join(args))
109 self.__logOutput(self.tr("Executing: {0}\n").format(
110 self.__cmd))
111 self.__process.start(prog, args)
112 procStarted = self.__process.waitForStarted(5000)
113 if not procStarted:
114 self.__logOutput(self.tr("Failed\n\n"))
115 self.__nextAttempt()
116
117 def on_buttonBox_clicked(self, button):
118 """
119 Private slot called by a button of the button box clicked.
120
121 @param button button that was clicked (QAbstractButton)
122 """
123 if button == self.buttonBox.button(
124 QDialogButtonBox.StandardButton.Close
125 ):
126 self.accept()
127 elif button == self.buttonBox.button(
128 QDialogButtonBox.StandardButton.Cancel
129 ):
130 self.__finish(0, 0, giveUp=True)
131
132 def __finish(self, exitCode, exitStatus, giveUp=False):
133 """
134 Private slot called when the process finished.
135
136 It is called when the process finished or
137 the user pressed the button.
138
139 @param exitCode exit code of the process (integer)
140 @param exitStatus exit status of the process (QProcess.ExitStatus)
141 @param giveUp flag indicating to not start another attempt (boolean)
142 """
143 if (
144 self.__process is not None and
145 self.__process.state() != QProcess.ProcessState.NotRunning
146 ):
147 self.__process.terminate()
148 QTimer.singleShot(2000, self.__process.kill)
149 self.__process.waitForFinished(3000)
150
151 self.buttonBox.button(
152 QDialogButtonBox.StandardButton.Close).setEnabled(True)
153 self.buttonBox.button(
154 QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
155 self.buttonBox.button(
156 QDialogButtonBox.StandardButton.Close).setDefault(True)
157
158 if not giveUp:
159 if exitCode != 0:
160 self.__logOutput(self.tr("Failed\n\n"))
161 if len(self.errors.toPlainText().splitlines()) == 1:
162 self.errors.clear()
163 self.errorGroup.hide()
164 self.__nextAttempt()
165 return
166
167 self.__process = None
168
169 if self.__pyvenv:
170 self.__logOutput(self.tr('\npyvenv finished.\n'))
171 else:
172 self.__logOutput(self.tr('\nvirtualenv finished.\n'))
173
174 if os.path.exists(self.__targetDir):
175 if self.__createScript:
176 self.__writeScriptFile()
177
178 if self.__createLog:
179 self.__writeLogFile()
180
181 if self.__openTarget:
182 QDesktopServices.openUrl(QUrl.fromLocalFile(
183 self.__targetDir))
184
185 self.__venvManager.addVirtualEnv(self.__venvName,
186 self.__targetDir)
187
188 def __nextAttempt(self):
189 """
190 Private method to start another attempt.
191 """
192 self.__callIndex += 1
193 if self.__callIndex < len(self.__calls):
194 self.start(self.__callArgs)
195 else:
196 if self.__pyvenv:
197 self.__logError(
198 self.tr('No suitable pyvenv program could be'
199 ' started.\n'))
200 else:
201 self.__logError(
202 self.tr('No suitable virtualenv program could be'
203 ' started.\n'))
204 self.__cmd = ""
205 self.__finish(0, 0, giveUp=True)
206
207 def __readStdout(self):
208 """
209 Private slot to handle the readyReadStandardOutput signal.
210
211 It reads the output of the process, formats it and inserts it into
212 the contents pane.
213 """
214 self.__process.setReadChannel(QProcess.ProcessChannel.StandardOutput)
215
216 while self.__process.canReadLine():
217 s = str(self.__process.readLine(),
218 Preferences.getSystem("IOEncoding"),
219 'replace')
220 self.__logOutput(s)
221
222 def __readStderr(self):
223 """
224 Private slot to handle the readyReadStandardError signal.
225
226 It reads the error output of the process and inserts it into the
227 error pane.
228 """
229 self.__process.setReadChannel(QProcess.ProcessChannel.StandardError)
230
231 while self.__process.canReadLine():
232 s = str(self.__process.readLine(),
233 Preferences.getSystem("IOEncoding"),
234 'replace')
235 self.__logError(s)
236
237 def __logOutput(self, s):
238 """
239 Private method to log some output.
240
241 @param s output string to log (string)
242 """
243 self.contents.insertPlainText(s)
244 self.contents.ensureCursorVisible()
245
246 def __logError(self, s):
247 """
248 Private method to log an error.
249
250 @param s error string to log (string)
251 """
252 self.errorGroup.show()
253 self.errors.insertPlainText(s)
254 self.errors.ensureCursorVisible()
255
256 def __writeLogFile(self):
257 """
258 Private method to write a log file to the virtualenv directory.
259 """
260 outtxt = self.contents.toPlainText()
261 logFile = (
262 os.path.join(self.__targetDir, "pyvenv.log")
263 if self.__pyvenv else
264 os.path.join(self.__targetDir, "virtualenv.log")
265 )
266 self.__logOutput(self.tr("\nWriting log file '{0}'.\n")
267 .format(logFile))
268
269 try:
270 with open(logFile, "w", encoding="utf-8") as f:
271 f.write(self.tr("Output:\n"))
272 f.write(outtxt)
273 errtxt = self.errors.toPlainText()
274 if errtxt:
275 f.write("\n")
276 f.write(self.tr("Errors:\n"))
277 f.write(errtxt)
278 except OSError as err:
279 self.__logError(
280 self.tr("""The logfile '{0}' could not be written.\n"""
281 """Reason: {1}\n""").format(logFile, str(err)))
282 self.__logOutput(self.tr("Done.\n"))
283
284 def __writeScriptFile(self):
285 """
286 Private method to write a script file to the virtualenv directory.
287 """
288 basename = "create_pyvenv" if self.__pyvenv else "create_virtualenv"
289 if isWindowsPlatform():
290 script = os.path.join(self.__targetDir, basename + ".cmd")
291 txt = self.__cmd
292 else:
293 script = os.path.join(self.__targetDir, basename + ".sh")
294 txt = "#!/usr/bin/env sh\n\n" + self.__cmd
295
296 self.__logOutput(self.tr("\nWriting script file '{0}'.\n")
297 .format(script))
298
299 try:
300 with open(script, "w", encoding="utf-8") as f:
301 f.write(txt)
302 except OSError as err:
303 self.__logError(
304 self.tr("""The script file '{0}' could not be written.\n"""
305 """Reason: {1}\n""").format(script, str(err)))
306 self.__logOutput(self.tr("Done.\n"))

eric ide

mercurial