eric7/VirtualEnv/VirtualenvExecDialog.py

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

eric ide

mercurial