--- a/Project/Project.py Sun Apr 15 12:04:17 2018 +0200 +++ b/Project/Project.py Tue Apr 17 19:53:46 2018 +0200 @@ -22,7 +22,7 @@ import zipfile from PyQt5.QtCore import pyqtSlot, QFile, QFileInfo, pyqtSignal, \ - QCryptographicHash, QIODevice, QByteArray, QObject, Qt + QCryptographicHash, QIODevice, QByteArray, QObject, Qt, QProcess from PyQt5.QtGui import QCursor, QKeySequence from PyQt5.QtWidgets import QLineEdit, QToolBar, QDialog, QInputDialog, \ QApplication, QMenu, QAction @@ -179,6 +179,7 @@ } self.vcsMenu = None + self.__makeProcess = None self.__initProjectTypes() @@ -4155,7 +4156,8 @@ """<p>This questions 'make', if a rebuild of the configured""" """ target is necessary.</p>""" )) - self.makeTestAct.triggered.connect(self.__questionMake) + self.makeTestAct.triggered.connect( + lambda: self.__executeMake(questionOnly=True)) self.actions.append(self.makeTestAct) self.closeAct.setEnabled(False) @@ -5407,32 +5409,6 @@ """ self.showMenu.emit("Make", self.makeMenu) - @pyqtSlot() - def executeMake(self): - """ - Public slot to execute a project specific make run (auto-run). - """ - self.__executeMake(automatic=True) - - @pyqtSlot() - def __executeMake(self, automatic=False): - """ - Private method to execute a project specific make run. - - @param automatic flag indicating a non-user invoked execution - @type bool - """ - # TODO: implement this - pass - - @pyqtSlot() - def __questionMake(self): - """ - Private method to question make for changes. - """ - # TODO: implement this - pass - def hasDefaultMakeParameters(self): """ Private method to test, if the project contains the default make @@ -5458,3 +5434,149 @@ @rtype bool """ return self.pdata["MAKEPARAMS"]["MakeEnabled"] + + @pyqtSlot() + def executeMake(self): + """ + Public slot to execute a project specific make run (auto-run) + (execute or question). + """ + self.__executeMake( + questionOnly=self.pdata["MAKEPARAMS"]["MakeTestOnly"], + interactive=False) + + @pyqtSlot() + def __executeMake(self, questionOnly=False, interactive=True): + """ + Private method to execute a project specific make run. + + @param questionOnly flag indicating to ask make for changes only + @type bool + @param interactive flag indicating an interactive invocation (i.e. + through a menu action) + @type bool + """ + if not self.pdata["MAKEPARAMS"]["MakeEnabled"] or \ + self.__makeProcess is not None: + return + + if self.pdata["MAKEPARAMS"]["MakeExecutable"]: + prog = self.pdata["MAKEPARAMS"]["MakeExecutable"] + else: + prog = Project.DefaultMake + + args = [] + if self.pdata["MAKEPARAMS"]["MakeParameters"]: + args.extend(Utilities.parseOptionString( + self.pdata["MAKEPARAMS"]["MakeParameters"])) + + if self.pdata["MAKEPARAMS"]["MakeFile"]: + args.append("--makefile={0}".format( + self.pdata["MAKEPARAMS"]["MakeFile"])) + + if questionOnly: + args.append("--question") + + if self.pdata["MAKEPARAMS"]["MakeTarget"]: + args.append(self.pdata["MAKEPARAMS"]["MakeTarget"]) + + self.__makeProcess = QProcess(self) + self.__makeProcess.readyReadStandardOutput.connect( + self.__makeReadStdOut) + self.__makeProcess.readyReadStandardError.connect( + self.__makeReadStdErr) + self.__makeProcess.finished.connect( + lambda exitCode, exitStatus: self.__makeFinished( + exitCode, exitStatus, questionOnly, interactive)) + self.__makeProcess.setWorkingDirectory(self.getProjectPath()) + self.__makeProcess.start(prog, args) + + if not self.__makeProcess.waitForStarted(): + E5MessageBox.critical( + self.ui, + self.tr("Execute Make"), + self.tr("""The make process did not start.""")) + + self.__cleanupMake() + + @pyqtSlot() + def __makeReadStdOut(self): + """ + Private slot to process process output received via stdout. + """ + if self.__makeProcess is not None: + output = str(self.__makeProcess.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace') + self.appendStdout.emit(output) + + @pyqtSlot() + def __makeReadStdErr(self): + """ + Private slot to process process output received via stderr. + """ + if self.__makeProcess is not None: + error = str(self.__makeProcess.readAllStandardError(), + Preferences.getSystem("IOEncoding"), + 'replace') + self.appendStderr.emit(error) + + def __makeFinished(self, exitCode, exitStatus, questionOnly, + interactive=True): + """ + Private slot handling the make process finished signal. + + @param exitCode exit code of the make process + @type int + @param exitStatus exit status of the make process + @type QProcess.ExitStatus + @param questionOnly flag indicating a test only run + @type bool + @param interactive flag indicating an interactive invocation (i.e. + through a menu action) + @type bool + """ + if exitStatus == QProcess.CrashExit: + E5MessageBox.critical( + self.ui, + self.tr("Execute Make"), + self.tr("""The make process crashed.""")) + else: + if questionOnly and exitCode == 1: + # a rebuild is needed + title = self.tr("Test for Changes") + + if self.pdata["MAKEPARAMS"]["MakeTarget"]: + message = self.tr( + """<p>There are changes that require the configured""" + """ make target <b>{0}</b> to be rebuilt.</p>""")\ + .format(self.pdata["MAKEPARAMS"]["MakeTarget"]) + else: + message = self.tr( + """<p>There are changes that require the default""" + """ make target to be rebuilt.</p>""") + + if self.ui.notificationsEnabled() and not interactive: + self.ui.showNotification( + UI.PixmapCache.getPixmap("makefile48.png"), + title, + message) + else: + E5MessageBox.information(self.ui, title, message) + elif exitCode > 1: + E5MessageBox.critical( + self.ui, + self.tr("Execute Make"), + self.tr("""The makefile contains errors.""")) + + self.__cleanupMake() + + def __cleanupMake(self): + """ + Private method to clean up make related stuff. + """ + self.__makeProcess.readyReadStandardOutput.disconnect() + self.__makeProcess.readyReadStandardError.disconnect() + self.__makeProcess.finished.disconnect() + self.__makeProcess.deleteLater() + self.__makeProcess = None