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