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 |
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)) |