--- a/PluginPyInstaller.py Tue Jan 16 15:36:57 2018 +0100 +++ b/PluginPyInstaller.py Wed Jan 17 16:25:59 2018 +0100 @@ -11,8 +11,14 @@ import os import platform +import shutil -from PyQt5.QtCore import QObject, QCoreApplication +from PyQt5.QtCore import pyqtSlot, QObject, QCoreApplication, QTranslator +from PyQt5.QtWidgets import QDialog + +from E5Gui import E5MessageBox +from E5Gui.E5Action import E5Action +from E5Gui.E5Application import e5App import Utilities @@ -154,10 +160,7 @@ # # Linux, Unix ... pyinstallerScripts = ['pyinstaller', 'pyi-makespec'] -## scriptSuffixes = [""] -## for minorVersion in minorVersions: -## scriptSuffixes.append( -## "-python{0}.{1}".format(majorVersion, minorVersion)) + # There could be multiple pyinstaller executables in the path # e.g. for different python variants path = Utilities.getEnvironmentEntry('PATH') @@ -232,30 +235,246 @@ class PyInstallerPlugin(QObject): """ - Class documentation goes here. + Class implementing the PyInstaller interface plug-in. """ def __init__(self, ui): """ Constructor - @param ui reference to the user interface object (UI.UserInterface) + @param ui reference to the user interface object + @type UI.UserInterface """ super(PyInstallerPlugin, self).__init__(ui) self.__ui = ui + + self.__initialize() + _checkProgram() + + self.__translator = None + self.__loadTranslator() + + def __initialize(self): + """ + Private slot to (re)initialize the plug-in. + """ + self.__projectActs = [] def activate(self): """ - Public method to activate this plugin. + Public method to activate this plug-in. @return tuple of None and activation status (boolean) """ global error - error = "" # clear previous error + + # There is already an error, don't activate + if error: + return None, False + + # pyinstaller interface is only activated if it is available + if not _checkProgram(): + return None, False + + # clear previous error + error = "" + + project = e5App().getObject("Project") + menu = project.getMenu("Packagers") + if menu: + # Execute PyInstaller + act = E5Action( + self.tr('Execute PyInstaller'), + self.tr('Execute Py&Installer'), 0, 0, + self, 'packagers_pyinstaller_run') + act.setStatusTip( + self.tr('Generate a distribution package using PyInstaller')) + act.setWhatsThis(self.tr( + """<b>Execute PyInstaller</b>""" + """<p>Generate a distribution package using PyInstaller.""" + """ The command is executed in the project path. All""" + """ files and directories must be given absolute or""" + """ relative to the project directory.</p>""" + )) + act.triggered.connect(self.__pyinstaller) + menu.addAction(act) + self.__projectActs.append(act) + + # Execute pyi-makespec + act = E5Action( + self.tr('Make PyInstaller Spec File'), + self.tr('Make PyInstaller &Spec File'), 0, 0, + self, 'packagers_pyinstaller_spec') + act.setStatusTip( + self.tr('Generate a spec file to be used by PyInstaller')) + act.setWhatsThis(self.tr( + """<b>Make PyInstaller Spec File</b>""" + """<p>Generate a spec file to be used by PyInstaller""" + """ PyInstaller. The command is executed in the project""" + """ path. All files and directories must be given absolute""" + """ or relative to the project directory.</p>""" + )) + act.triggered.connect(self.__pyiMakeSpec) + menu.addAction(act) + self.__projectActs.append(act) + + # clean the pyinstaller created directories + act = E5Action( + self.tr('Clean PyInstaller'), + self.tr('&Clean PyInstaller'), 0, 0, + self, 'packagers_pyinstaller_clean') + act.setStatusTip( + self.tr('Remove the PyInstaller created directories')) + act.setWhatsThis(self.tr( + """<b>Clean PyInstaller</b>""" + """<p>Remove the PyInstaller created directories (dist and""" + """ build). These are subdirectories within the project""" + """ path.""" + )) + act.triggered.connect(self.__pyinstallerCleanup) + menu.addAction(act) + self.__projectActs.append(act) + + project.addE5Actions(self.__projectActs) + project.showMenu.connect(self.__projectShowMenu) return None, True def deactivate(self): """ - Public method to deactivate this plugin. + Public method to deactivate this plug-in. + """ + menu = e5App().getObject("Project").getMenu("Packagers") + if menu: + for act in self.__projectActs: + menu.removeAction(act) + + e5App().getObject("Project").removeE5Actions( + self.__projectActs) + + self.__initialize() + + def __projectShowMenu(self, menuName, menu): + """ + Private slot called, when the the project menu or a submenu is + about to be shown. + + @param menuName name of the menu to be shown + @type str + @param menu reference to the menu + @type QMenu + """ + if menuName == "Packagers": + enable = e5App().getObject("Project").getProjectLanguage() in \ + ["Python", "Python2", "Python3"] + for act in self.__projectActs: + act.setEnabled(enable) + + def __loadTranslator(self): + """ + Private method to load the translation file. + """ + if self.__ui is not None: + loc = self.__ui.getLocale() + if loc and loc != "C": + locale_dir = \ + os.path.join(os.path.dirname(__file__), + "PyInstaller", "i18n") + translation = "pyinstaller_{0}".format(loc) + translator = QTranslator(None) + loaded = translator.load(translation, locale_dir) + if loaded: + self.__translator = translator + e5App().installTranslator(self.__translator) + else: + print("Warning: translation file '{0}' could not be" + " loaded.".format(translation)) + print("Using default.") + + @pyqtSlot() + def __pyinstaller(self): + """ + Private slot to execute the pyinstaller command for the current + project. """ - pass + project = e5App().getObject("Project") + majorVersionStr = project.getProjectLanguage() + if majorVersionStr == "Python3": + executables = [f for f in exePy3 if + f.endswith(("pyinstaller", "pyinstaller.exe"))] + else: + executables = [f for f in exePy2 if + f.endswith(("pyinstaller", "pyinstaller.exe"))] + if not executables: + E5MessageBox.critical( + self.__ui, + self.tr("pyinstaller"), + self.tr("""The pyinstaller executable could not be found.""")) + return + + # check if all files saved and errorfree before continue + if not project.checkAllScriptsDirty(reportSyntaxErrors=True): + return + + from PyInstaller.PyInstallerConfigDialog import PyInstallerConfigDialog + params = project.getData('PACKAGERSPARMS', "PYINSTALLER") + dlg = PyInstallerConfigDialog(project, executables, params, + mode="installer") + if dlg.exec_() == QDialog.Accepted: + args, params = dlg.generateParameters() + project.setData('PACKAGERSPARMS', "PYINSTALLER", params) + + # now do the call + from PyInstaller.PyInstallerExecDialog import PyInstallerExecDialog + dia = PyInstallerExecDialog("pyinstaller") + dia.show() + res = dia.start(args, params, project.getProjectPath(), + project.getMainScript()) + if res: + dia.exec_() + # TODO: implement pyinstaller + + @pyqtSlot() + def __pyiMakeSpec(self): + """ + Private slot to execute the pyi-makespec command for the current + project to generate a spec file to be used by pyinstaller. + """ + project = e5App().getObject("Project") + majorVersionStr = project.getProjectLanguage() + if majorVersionStr == "Python3": + executables = [f for f in exePy3 if + f.endswith(("pyi-makespec", "pyi-makespec.exe"))] + else: + executables = [f for f in exePy2 if + f.endswith(("pyi-makespec", "pyi-makespec.exe"))] + if not executables: + E5MessageBox.critical( + self.__ui, + self.tr("pyi-makespec"), + self.tr("""The pyi-makespec executable could not be found.""")) + return + + # check if all files saved and errorfree before continue + if not project.checkAllScriptsDirty(reportSyntaxErrors=True): + return + + # TODO: implement pyi-makespec + + @pyqtSlot() + def __pyinstallerCleanup(self): + """ + Private slot to remove the directories created by pyinstaller. + """ + project = e5App().getObject("Project") + + from PyInstaller.PyInstallerCleanupDialog import \ + PyInstallerCleanupDialog + dlg = PyInstallerCleanupDialog() + if dlg.exec_() == QDialog.Accepted: + removeDirs = dlg.getDirectories() + for directory in removeDirs: + rd = os.path.join(project.getProjectPath(), directory) + shutil.rmtree(rd, ignore_errors=True) + +# +# eflag: noqa = M801