CxFreeze/CxfreezeExecDialog.py

branch
eric7
changeset 139
4df5e67b084b
parent 137
da8cc28e689f
child 140
9e20ee9c7ca2
equal deleted inserted replaced
138:54f57d052146 139:4df5e67b084b
23 23
24 24
25 class CxfreezeExecDialog(QDialog, Ui_CxfreezeExecDialog): 25 class CxfreezeExecDialog(QDialog, Ui_CxfreezeExecDialog):
26 """ 26 """
27 Class implementing a dialog to show the output of the cxfreeze process. 27 Class implementing a dialog to show the output of the cxfreeze process.
28 28
29 This class starts a QProcess and displays a dialog that 29 This class starts a QProcess and displays a dialog that
30 shows the output of the packager command process. 30 shows the output of the packager command process.
31 """ 31 """
32
32 def __init__(self, cmdname, parent=None): 33 def __init__(self, cmdname, parent=None):
33 """ 34 """
34 Constructor 35 Constructor
35 36
36 @param cmdname name of the packager (string) 37 @param cmdname name of the packager (string)
37 @param parent parent widget of this dialog (QWidget) 38 @param parent parent widget of this dialog (QWidget)
38 """ 39 """
39 super().__init__(parent) 40 super().__init__(parent)
40 self.setupUi(self) 41 self.setupUi(self)
41 42
42 self.buttonBox.button( 43 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
43 QDialogButtonBox.StandardButton.Close).setEnabled(False) 44 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True)
44 self.buttonBox.button( 45
45 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
46
47 self.process = None 46 self.process = None
48 self.copyProcess = None 47 self.copyProcess = None
49 self.cmdname = cmdname 48 self.cmdname = cmdname
50 49
51 def start(self, args, parms, ppath, mainscript): 50 def start(self, args, parms, ppath, mainscript):
52 """ 51 """
53 Public slot to start the packager command. 52 Public slot to start the packager command.
54 53
55 @param args commandline arguments for packager program 54 @param args commandline arguments for packager program
56 @type list of str 55 @type list of str
57 @param parms parameters got from the config dialog 56 @param parms parameters got from the config dialog
58 @type dict 57 @type dict
59 @param ppath project path 58 @param ppath project path
65 """ 64 """
66 self.errorGroup.hide() 65 self.errorGroup.hide()
67 script = os.path.join(ppath, mainscript) 66 script = os.path.join(ppath, mainscript)
68 dname = os.path.dirname(script) 67 dname = os.path.dirname(script)
69 script = os.path.basename(script) 68 script = os.path.basename(script)
70 69
71 self.ppath = ppath 70 self.ppath = ppath
72 self.additionalFiles = parms.get('additionalFiles', []) 71 self.additionalFiles = parms.get("additionalFiles", [])
73 self.targetDirectory = os.path.join( 72 self.targetDirectory = os.path.join(parms.get("targetDirectory", "dist"))
74 parms.get('targetDirectory', 'dist')) 73
75
76 self.contents.clear() 74 self.contents.clear()
77 self.errors.clear() 75 self.errors.clear()
78 76
79 args.append(script) 77 args.append(script)
80 78
81 self.process = QProcess() 79 self.process = QProcess()
82 self.process.setWorkingDirectory(dname) 80 self.process.setWorkingDirectory(dname)
83 81
84 self.process.readyReadStandardOutput.connect(self.__readStdout) 82 self.process.readyReadStandardOutput.connect(self.__readStdout)
85 self.process.readyReadStandardError.connect(self.__readStderr) 83 self.process.readyReadStandardError.connect(self.__readStderr)
86 self.process.finished.connect(self.__finishedFreeze) 84 self.process.finished.connect(self.__finishedFreeze)
87 85
88 self.setWindowTitle(self.tr('{0} - {1}').format( 86 self.setWindowTitle(self.tr("{0} - {1}").format(self.cmdname, script))
89 self.cmdname, script)) 87 self.contents.insertPlainText(" ".join(args) + "\n\n")
90 self.contents.insertPlainText(' '.join(args) + '\n\n')
91 self.contents.ensureCursorVisible() 88 self.contents.ensureCursorVisible()
92 89
93 program = args.pop(0) 90 program = args.pop(0)
94 self.process.start(program, args) 91 self.process.start(program, args)
95 procStarted = self.process.waitForStarted() 92 procStarted = self.process.waitForStarted()
96 if not procStarted: 93 if not procStarted:
97 EricMessageBox.critical( 94 EricMessageBox.critical(
98 self, 95 self,
99 self.tr('Process Generation Error'), 96 self.tr("Process Generation Error"),
100 self.tr( 97 self.tr(
101 'The process {0} could not be started. ' 98 "The process {0} could not be started. "
102 'Ensure, that it is in the search path.' 99 "Ensure, that it is in the search path."
103 ).format(program)) 100 ).format(program),
101 )
104 return procStarted 102 return procStarted
105 103
106 @pyqtSlot(QAbstractButton) 104 @pyqtSlot(QAbstractButton)
107 def on_buttonBox_clicked(self, button): 105 def on_buttonBox_clicked(self, button):
108 """ 106 """
109 Private slot called by a button of the button box clicked. 107 Private slot called by a button of the button box clicked.
110 108
111 @param button button that was clicked 109 @param button button that was clicked
112 @type QAbstractButton 110 @type QAbstractButton
113 """ 111 """
114 if button == self.buttonBox.button( 112 if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close):
115 QDialogButtonBox.StandardButton.Close
116 ):
117 self.accept() 113 self.accept()
118 elif button == self.buttonBox.button( 114 elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel):
119 QDialogButtonBox.StandardButton.Cancel 115 self.additionalFiles = [] # Skip copying additional files
120 ):
121 self.additionalFiles = [] # Skip copying additional files
122 self.__finish() 116 self.__finish()
123 117
124 def __finish(self): 118 def __finish(self):
125 """ 119 """
126 Private slot called when the process finished. 120 Private slot called when the process finished.
127 121
128 It is called when the process finished or the user pressed the 122 It is called when the process finished or the user pressed the
129 cancel button. 123 cancel button.
130 """ 124 """
131 if self.process is not None: 125 if self.process is not None:
132 self.process.disconnect(self.__finishedFreeze) 126 self.process.disconnect(self.__finishedFreeze)
133 self.process.terminate() 127 self.process.terminate()
134 QTimer.singleShot(2000, self.process.kill) 128 QTimer.singleShot(2000, self.process.kill)
135 self.process.waitForFinished(3000) 129 self.process.waitForFinished(3000)
136 self.process = None 130 self.process = None
137 131
138 if self.copyProcess is not None: 132 if self.copyProcess is not None:
139 self.copyProcess.terminate() 133 self.copyProcess.terminate()
140 self.copyProcess = None 134 self.copyProcess = None
141 135
142 self.contents.insertPlainText( 136 self.contents.insertPlainText(self.tr("\n{0} aborted.\n").format(self.cmdname))
143 self.tr('\n{0} aborted.\n').format(self.cmdname)) 137
144
145 self.__enableButtons() 138 self.__enableButtons()
146 139
147 def __finishedFreeze(self): 140 def __finishedFreeze(self):
148 """ 141 """
149 Private slot called when the process finished. 142 Private slot called when the process finished.
150 143
151 It is called when the process finished or the user pressed the 144 It is called when the process finished or the user pressed the
152 cancel button. 145 cancel button.
153 """ 146 """
154 self.process = None 147 self.process = None
155 148
156 self.contents.insertPlainText( 149 self.contents.insertPlainText(self.tr("\n{0} finished.\n").format(self.cmdname))
157 self.tr('\n{0} finished.\n').format(self.cmdname)) 150
158
159 self.copyProcess = CopyAdditionalFiles(self) 151 self.copyProcess = CopyAdditionalFiles(self)
160 self.copyProcess.insertPlainText.connect(self.contents.insertPlainText) 152 self.copyProcess.insertPlainText.connect(self.contents.insertPlainText)
161 self.copyProcess.finished.connect(self.__enableButtons) 153 self.copyProcess.finished.connect(self.__enableButtons)
162 self.copyProcess.start() 154 self.copyProcess.start()
163 155
164 def __enableButtons(self): 156 def __enableButtons(self):
165 """ 157 """
166 Private slot called when all processes finished. 158 Private slot called when all processes finished.
167 159
168 It is called when the process finished or 160 It is called when the process finished or
169 the user pressed the cancel button. 161 the user pressed the cancel button.
170 """ 162 """
171 self.copyProcess = None 163 self.copyProcess = None
172 self.buttonBox.button( 164 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True)
173 QDialogButtonBox.StandardButton.Close).setEnabled(True) 165 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
174 self.buttonBox.button( 166 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True)
175 QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
176 self.buttonBox.button(
177 QDialogButtonBox.StandardButton.Close).setDefault(True)
178 self.contents.ensureCursorVisible() 167 self.contents.ensureCursorVisible()
179 168
180 def __readStdout(self): 169 def __readStdout(self):
181 """ 170 """
182 Private slot to handle the readyReadStandardOutput signal. 171 Private slot to handle the readyReadStandardOutput signal.
183 172
184 It reads the output of the process, formats it and inserts it into 173 It reads the output of the process, formats it and inserts it into
185 the contents pane. 174 the contents pane.
186 """ 175 """
187 self.process.setReadChannel(QProcess.ProcessChannel.StandardOutput) 176 self.process.setReadChannel(QProcess.ProcessChannel.StandardOutput)
188 177
189 while self.process.canReadLine(): 178 while self.process.canReadLine():
190 s = str(self.process.readAllStandardOutput(), 179 s = str(
191 Preferences.getSystem("IOEncoding"), 180 self.process.readAllStandardOutput(),
192 'replace') 181 Preferences.getSystem("IOEncoding"),
182 "replace",
183 )
193 self.contents.insertPlainText(s) 184 self.contents.insertPlainText(s)
194 self.contents.ensureCursorVisible() 185 self.contents.ensureCursorVisible()
195 186
196 def __readStderr(self): 187 def __readStderr(self):
197 """ 188 """
198 Private slot to handle the readyReadStandardError signal. 189 Private slot to handle the readyReadStandardError signal.
199 190
200 It reads the error output of the process and inserts it into the 191 It reads the error output of the process and inserts it into the
201 error pane. 192 error pane.
202 """ 193 """
203 self.process.setReadChannel(QProcess.ProcessChannel.StandardError) 194 self.process.setReadChannel(QProcess.ProcessChannel.StandardError)
204 195
205 while self.process.canReadLine(): 196 while self.process.canReadLine():
206 self.errorGroup.show() 197 self.errorGroup.show()
207 s = str(self.process.readAllStandardError(), 198 s = str(
208 Preferences.getSystem("IOEncoding"), 199 self.process.readAllStandardError(),
209 'replace') 200 Preferences.getSystem("IOEncoding"),
201 "replace",
202 )
210 self.errors.insertPlainText(s) 203 self.errors.insertPlainText(s)
211 self.errors.ensureCursorVisible() 204 self.errors.ensureCursorVisible()
212 205
213 206
214 class CopyAdditionalFiles(QThread): 207 class CopyAdditionalFiles(QThread):
215 """ 208 """
216 Thread to copy the distribution dependent files. 209 Thread to copy the distribution dependent files.
217 210
218 @signal insertPlainText(text) emitted to inform user about the copy 211 @signal insertPlainText(text) emitted to inform user about the copy
219 progress 212 progress
220 """ 213 """
214
221 insertPlainText = pyqtSignal(str) 215 insertPlainText = pyqtSignal(str)
222 216
223 def __init__(self, main): 217 def __init__(self, main):
224 """ 218 """
225 Constructor 219 Constructor
226 220
227 @param main self-object of the caller 221 @param main self-object of the caller
228 """ 222 """
229 super().__init__() 223 super().__init__()
230 224
231 self.ppath = main.ppath 225 self.ppath = main.ppath
232 self.additionalFiles = main.additionalFiles 226 self.additionalFiles = main.additionalFiles
233 self.targetDirectory = main.targetDirectory 227 self.targetDirectory = main.targetDirectory
234 228
235 def __copytree(self, src, dst): 229 def __copytree(self, src, dst):
236 """ 230 """
237 Private method to copy a file or folder. 231 Private method to copy a file or folder.
238 232
239 Wildcards allowed. Existing files are overwitten. 233 Wildcards allowed. Existing files are overwitten.
240 234
241 @param src source file or folder to copy. Wildcards allowed. 235 @param src source file or folder to copy. Wildcards allowed.
242 @type str 236 @type str
243 @param dst destination 237 @param dst destination
244 @type str 238 @type str
245 @exception OSError raised if there is an issue writing the package or 239 @exception OSError raised if there is an issue writing the package or
248 # __IGNORE_WARNING_D234r__ __IGNORE_WARNING_D252__ 242 # __IGNORE_WARNING_D234r__ __IGNORE_WARNING_D252__
249 def src2dst(srcname, base, dst): 243 def src2dst(srcname, base, dst):
250 """ 244 """
251 Combines the relativ path of the source (srcname) with the 245 Combines the relativ path of the source (srcname) with the
252 destination folder. 246 destination folder.
253 247
254 @param srcname actual file or folder to copy 248 @param srcname actual file or folder to copy
255 @type str 249 @type str
256 @param base basename of the source folder 250 @param base basename of the source folder
257 @type str 251 @type str
258 @param dst basename of the destination folder 252 @param dst basename of the destination folder
260 @return destination path 254 @return destination path
261 @rtype str 255 @rtype str
262 """ 256 """
263 delta = srcname.split(base)[1] 257 delta = srcname.split(base)[1]
264 return os.path.join(dst, delta[1:]) 258 return os.path.join(dst, delta[1:])
265 259
266 base, fileOrFolderName = os.path.split(src) 260 base, fileOrFolderName = os.path.split(src)
267 initDone = False 261 initDone = False
268 for root, dirs, files in os.walk(base): 262 for root, dirs, files in os.walk(base):
269 copied = False 263 copied = False
270 # remove all none matching directory names, create all others 264 # remove all none matching directory names, create all others
283 if err.errno != errno.EEXIST: 277 if err.errno != errno.EEXIST:
284 # it's ok if directory already exists 278 # it's ok if directory already exists
285 raise err 279 raise err
286 else: 280 else:
287 dirs.remove(directory) 281 dirs.remove(directory)
288 282
289 for file in files: 283 for file in files:
290 fn = os.path.join(root, file) 284 fn = os.path.join(root, file)
291 if initDone or fnmatch.fnmatch(fn, src): 285 if initDone or fnmatch.fnmatch(fn, src):
292 newFile = src2dst(fn, base, dst) 286 newFile = src2dst(fn, base, dst)
293 # copy files, give errors to caller 287 # copy files, give errors to caller
294 shutil.copy2(fn, newFile) 288 shutil.copy2(fn, newFile)
295 copied = True 289 copied = True
296 290
297 # check if file was found and copied 291 # check if file was found and copied
298 if len(files) and not copied: 292 if len(files) and not copied:
299 raise OSError( 293 raise OSError(
300 errno.ENOENT, 294 errno.ENOENT,
301 self.tr("No such file or directory: '{0}'") 295 self.tr("No such file or directory: '{0}'").format(src),
302 .format(src)) 296 )
303 297
304 initDone = True 298 initDone = True
305 299
306 def run(self): 300 def run(self):
307 """ 301 """
308 Public method to run the thread. 302 Public method to run the thread.
309 303
310 QThread entry point to copy the selected additional files and folders. 304 QThread entry point to copy the selected additional files and folders.
311 305
312 @exception OSError raised if there is an issue writing the package 306 @exception OSError raised if there is an issue writing the package
313 """ # __IGNORE_WARNING_D252__ __IGNORE_WARNING_D253__ 307 """ # __IGNORE_WARNING_D252__ __IGNORE_WARNING_D253__
314 self.insertPlainText.emit('----\n') 308 self.insertPlainText.emit("----\n")
315 os.chdir(self.ppath) 309 os.chdir(self.ppath)
316 for fn in self.additionalFiles: 310 for fn in self.additionalFiles:
317 self.insertPlainText.emit( 311 self.insertPlainText.emit(self.tr("\nCopying {0}: ").format(fn))
318 self.tr('\nCopying {0}: ').format(fn)) 312
319
320 # on linux normpath doesn't replace backslashes to slashes. 313 # on linux normpath doesn't replace backslashes to slashes.
321 fn = fn.replace('\\', '/') 314 fn = fn.replace("\\", "/")
322 fn = os.path.abspath(os.path.normpath(fn)) 315 fn = os.path.abspath(os.path.normpath(fn))
323 dst = os.path.join(self.ppath, self.targetDirectory) 316 dst = os.path.join(self.ppath, self.targetDirectory)
324 if fn.startswith(os.path.normpath(self.ppath)): 317 if fn.startswith(os.path.normpath(self.ppath)):
325 dirname = fn.split(self.ppath + os.sep)[1] 318 dirname = fn.split(self.ppath + os.sep)[1]
326 dst = os.path.join(dst, os.path.dirname(dirname)) 319 dst = os.path.join(dst, os.path.dirname(dirname))
328 os.makedirs(dst) 321 os.makedirs(dst)
329 except OSError as err: 322 except OSError as err:
330 if err.errno != errno.EEXIST: 323 if err.errno != errno.EEXIST:
331 # it's ok if directory already exists 324 # it's ok if directory already exists
332 raise err 325 raise err
333 326
334 try: 327 try:
335 self.__copytree(fn, dst) 328 self.__copytree(fn, dst)
336 self.insertPlainText.emit(self.tr('ok')) 329 self.insertPlainText.emit(self.tr("ok"))
337 except OSError as err: 330 except OSError as err:
338 self.insertPlainText.emit( 331 self.insertPlainText.emit(self.tr("failed: {0}").format(err.strerror))
339 self.tr('failed: {0}').format(err.strerror))

eric ide

mercurial