--- a/PluginPyInstaller.py Thu Dec 30 11:55:51 2021 +0100 +++ b/PluginPyInstaller.py Wed Sep 21 16:45:00 2022 +0200 @@ -12,9 +12,7 @@ import platform import shutil -from PyQt6.QtCore import ( - pyqtSlot, QObject, QCoreApplication, QTranslator, QProcess -) +from PyQt6.QtCore import pyqtSlot, QObject, QCoreApplication, QTranslator, QProcess from PyQt6.QtWidgets import QDialog from EricWidgets import EricMessageBox @@ -42,14 +40,14 @@ # End-Of-Header error = "" - + exePy3 = [] def exeDisplayDataList(): """ Module function to support the display of some executable info. - + @return list of dictionaries containing the data to query the presence of the executable @rtype list of dict @@ -58,7 +56,8 @@ data = { "programEntry": True, "header": QCoreApplication.translate( - "PyInstallerPlugin", "Packagers - PyInstaller"), + "PyInstallerPlugin", "Packagers - PyInstaller" + ), "exe": "dummyExe", "versionCommand": "--version", "versionStartsWith": "dummyExe", @@ -67,7 +66,7 @@ "versionCleanup": None, "versionRe": "^\\d", } - + if _checkProgram(): for exePath in exePy3: data["exe"] = exePath @@ -81,7 +80,7 @@ def _findExecutable(majorVersion): """ Restricted function to determine the names of the executables. - + @param majorVersion major python version @type int @return names of the executables @@ -92,7 +91,7 @@ minorVersions = range(16) else: return [] - + executables = set() if Utilities.isWindowsPlatform(): # @@ -101,61 +100,70 @@ try: import winreg except ImportError: - import _winreg as winreg # __IGNORE_WARNING__ - + import _winreg as winreg # __IGNORE_WARNING__ + def getExePath(branch, access, versionStr): exes = [] with contextlib.suppress(WindowsError, OSError): - software = winreg.OpenKey(branch, 'Software', 0, access) - python = winreg.OpenKey(software, 'Python', 0, access) - pcore = winreg.OpenKey(python, 'PythonCore', 0, access) + software = winreg.OpenKey(branch, "Software", 0, access) + python = winreg.OpenKey(software, "Python", 0, access) + pcore = winreg.OpenKey(python, "PythonCore", 0, access) version = winreg.OpenKey(pcore, versionStr, 0, access) - installpath = winreg.QueryValue(version, 'InstallPath') + installpath = winreg.QueryValue(version, "InstallPath") # Look for pyinstaller.exe - exe = os.path.join(installpath, 'Scripts', 'pyinstaller.exe') + exe = os.path.join(installpath, "Scripts", "pyinstaller.exe") if os.access(exe, os.X_OK): exes.append(exe) # Look for pyi-makespec.exe - exe = os.path.join(installpath, 'Scripts', 'pyi-makespec.exe') + exe = os.path.join(installpath, "Scripts", "pyi-makespec.exe") if os.access(exe, os.X_OK): exes.append(exe) return exes - + versionSuffixes = ["", "-32", "-64"] for minorVersion in minorVersions: for versionSuffix in versionSuffixes: - versionStr = '{0}.{1}{2}'.format(majorVersion, minorVersion, - versionSuffix) + versionStr = "{0}.{1}{2}".format( + majorVersion, minorVersion, versionSuffix + ) exePaths = getExePath( winreg.HKEY_CURRENT_USER, - winreg.KEY_WOW64_32KEY | winreg.KEY_READ, versionStr) + winreg.KEY_WOW64_32KEY | winreg.KEY_READ, + versionStr, + ) for exePath in exePaths: executables.add(exePath) - + exePaths = getExePath( winreg.HKEY_LOCAL_MACHINE, - winreg.KEY_WOW64_32KEY | winreg.KEY_READ, versionStr) + winreg.KEY_WOW64_32KEY | winreg.KEY_READ, + versionStr, + ) for exePath in exePaths: executables.add(exePath) - + # Even on Intel 64-bit machines it's 'AMD64' - if platform.machine() == 'AMD64': + if platform.machine() == "AMD64": exePaths = getExePath( winreg.HKEY_CURRENT_USER, - winreg.KEY_WOW64_64KEY | winreg.KEY_READ, versionStr) + winreg.KEY_WOW64_64KEY | winreg.KEY_READ, + versionStr, + ) for exePath in exePaths: executables.add(exePath) - + exePaths = getExePath( winreg.HKEY_LOCAL_MACHINE, - winreg.KEY_WOW64_64KEY | winreg.KEY_READ, versionStr) + winreg.KEY_WOW64_64KEY | winreg.KEY_READ, + versionStr, + ) for exePath in exePaths: executables.add(exePath) - + if not executables and majorVersion >= 3: # check the PATH environment variable if nothing was found # Python 3 only - path = Utilities.getEnvironmentEntry('PATH') + path = Utilities.getEnvironmentEntry("PATH") if path: dirs = path.split(os.pathsep) for directory in dirs: @@ -166,15 +174,15 @@ else: # # Linux, Unix ... - pyinstallerScripts = ['pyinstaller', 'pyi-makespec'] - + pyinstallerScripts = ["pyinstaller", "pyi-makespec"] + # There could be multiple pyinstaller executables in the path # e.g. for different python variants - path = Utilities.getEnvironmentEntry('PATH') + path = Utilities.getEnvironmentEntry("PATH") # environment variable not defined if path is None: return [] - + # step 1: determine possible candidates exes = [] dirs = path.split(os.pathsep) @@ -183,7 +191,7 @@ exe = os.path.join(directory, pyinstallerScript) if os.access(exe, os.X_OK): exes.append(exe) - + # step 2: determine the Python variant _exePy3 = set() versionArgs = ["-c", "import sys; print(sys.version_info[0])"] @@ -196,12 +204,12 @@ process.waitForFinished(5000) # get a QByteArray of the output versionBytes = process.readAllStandardOutput() - versionStr = str(versionBytes, encoding='utf-8').strip() + versionStr = str(versionBytes, encoding="utf-8").strip() if versionStr == "3": _exePy3.add(exe) - + executables = _exePy3 - + # sort items, the probably newest topmost executables = list(executables) executables.sort(reverse=True) @@ -211,23 +219,22 @@ def _checkProgram(): """ Restricted function to check the availability of pyinstaller. - + @return flag indicating availability @rtype bool """ global error, exePy3 - + exePy3 = _findExecutable(3) if not exePy3: if Utilities.isWindowsPlatform(): error = QCoreApplication.translate( "PyInstallerPlugin", - "The pyinstaller.exe executable could not be found." + "The pyinstaller.exe executable could not be found.", ) else: error = QCoreApplication.translate( - "PyInstallerPlugin", - "The pyinstaller executable could not be found." + "PyInstallerPlugin", "The pyinstaller executable could not be found." ) return False else: @@ -238,112 +245,130 @@ """ Class implementing the PyInstaller interface plug-in. """ + def __init__(self, ui): """ Constructor - + @param ui reference to the user interface object @type UI.UserInterface """ super().__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 = [] self.__projectSeparator = None - + def activate(self): """ Public method to activate this plug-in. - + @return tuple of None and activation status @rtype tuple of (None, bool) """ global 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 = ericApp().getObject("Project") menu = project.getMenu("Packagers") if menu: self.__projectSeparator = menu.addSeparator() - + # Execute PyInstaller act = EricAction( - self.tr('Execute PyInstaller'), - self.tr('Execute Py&Installer'), 0, 0, - self, 'packagers_pyinstaller_run') + 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 as absolute paths or""" - """ as paths relative to the project path.</p>""" - )) + 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 as absolute paths or""" + """ as paths relative to the project path.</p>""" + ) + ) act.triggered.connect(self.__pyinstaller) menu.addAction(act) self.__projectActs.append(act) - + # Execute pyi-makespec act = EricAction( - 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.""" - """ The command is executed in the project path. All""" - """ files and directories must be given as absolute paths or""" - """ as paths relative to the project path.</p>""" - )) + 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.""" + """ The command is executed in the project path. All""" + """ files and directories must be given as absolute paths or""" + """ as paths relative to the project path.</p>""" + ) + ) act.triggered.connect(self.__pyiMakeSpec) menu.addAction(act) self.__projectActs.append(act) - + # clean the pyinstaller created directories act = EricAction( - 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.</p>""" - )) + 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.</p>""" + ) + ) act.triggered.connect(self.__pyinstallerCleanup) menu.addAction(act) self.__projectActs.append(act) - + project.addEricActions(self.__projectActs) project.showMenu.connect(self.__projectShowMenu) - + return None, True - + def deactivate(self): """ Public method to deactivate this plug-in. @@ -354,30 +379,26 @@ menu.removeAction(act) if self.__projectSeparator: menu.removeAction(self.__projectSeparator) - - ericApp().getObject("Project").removeEricActions( - self.__projectActs) - + + ericApp().getObject("Project").removeEricActions(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 = ( - ericApp().getObject("Project").getProjectLanguage() == - "Python3" - ) + enable = ericApp().getObject("Project").getProjectLanguage() == "Python3" for act in self.__projectActs: act.setEnabled(enable) - + def __loadTranslator(self): """ Private method to load the translation file. @@ -385,8 +406,9 @@ if self.__ui is not None: loc = self.__ui.getLocale() if loc and loc != "C": - locale_dir = os.path.join(os.path.dirname(__file__), - "PyInstallerInterface", "i18n") + locale_dir = os.path.join( + os.path.dirname(__file__), "PyInstallerInterface", "i18n" + ) translation = "pyinstaller_{0}".format(loc) translator = QTranslator(None) loaded = translator.load(translation, locale_dir) @@ -394,10 +416,12 @@ self.__translator = translator ericApp().installTranslator(self.__translator) else: - print("Warning: translation file '{0}' could not be" - " loaded.".format(translation)) + print( + "Warning: translation file '{0}' could not be" + " loaded.".format(translation) + ) print("Using default.") - + @pyqtSlot() def __pyinstaller(self): """ @@ -407,40 +431,46 @@ project = ericApp().getObject("Project") majorVersionStr = project.getProjectLanguage() if majorVersionStr == "Python3": - executables = [f for f in exePy3 if - f.endswith(("pyinstaller", "pyinstaller.exe"))] + executables = [ + f for f in exePy3 if f.endswith(("pyinstaller", "pyinstaller.exe")) + ] if not executables: EricMessageBox.critical( self.__ui, self.tr("pyinstaller"), - self.tr("""The pyinstaller executable could not be""" - """ found.""")) + 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 PyInstallerInterface.PyInstallerConfigDialog import ( - PyInstallerConfigDialog + PyInstallerConfigDialog, ) - params = project.getData('PACKAGERSPARMS', "PYINSTALLER") - dlg = PyInstallerConfigDialog(project, executables, params, - mode="installer") + + params = project.getData("PACKAGERSPARMS", "PYINSTALLER") + dlg = PyInstallerConfigDialog( + project, executables, params, mode="installer" + ) if dlg.exec() == QDialog.DialogCode.Accepted: args, params, script = dlg.generateParameters() - project.setData('PACKAGERSPARMS', "PYINSTALLER", params) - + project.setData("PACKAGERSPARMS", "PYINSTALLER", params) + # now do the call from PyInstallerInterface.PyInstallerExecDialog import ( - PyInstallerExecDialog + PyInstallerExecDialog, ) + dia = PyInstallerExecDialog("pyinstaller") dia.show() res = dia.start(args, params, project, script) if res: dia.exec() - + @pyqtSlot() def __pyiMakeSpec(self): """ @@ -450,50 +480,55 @@ project = ericApp().getObject("Project") majorVersionStr = project.getProjectLanguage() if majorVersionStr == "Python3": - executables = [f for f in exePy3 if - f.endswith(("pyi-makespec", "pyi-makespec.exe"))] + executables = [ + f for f in exePy3 if f.endswith(("pyi-makespec", "pyi-makespec.exe")) + ] if not executables: EricMessageBox.critical( self.__ui, self.tr("pyi-makespec"), - self.tr("""The pyi-makespec executable could not be""" - """ found.""")) + 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 - + from PyInstallerInterface.PyInstallerConfigDialog import ( - PyInstallerConfigDialog + PyInstallerConfigDialog, ) - params = project.getData('PACKAGERSPARMS', "PYINSTALLER") - dlg = PyInstallerConfigDialog(project, executables, params, - mode="spec") + + params = project.getData("PACKAGERSPARMS", "PYINSTALLER") + dlg = PyInstallerConfigDialog(project, executables, params, mode="spec") if dlg.exec() == QDialog.DialogCode.Accepted: args, params, script = dlg.generateParameters() - project.setData('PACKAGERSPARMS', "PYINSTALLER", params) - + project.setData("PACKAGERSPARMS", "PYINSTALLER", params) + # now do the call from PyInstallerInterface.PyInstallerExecDialog import ( - PyInstallerExecDialog + PyInstallerExecDialog, ) + dia = PyInstallerExecDialog("pyinstaller") dia.show() res = dia.start(args, params, project, script) if res: dia.exec() - + @pyqtSlot() def __pyinstallerCleanup(self): """ Private slot to remove the directories created by pyinstaller. """ project = ericApp().getObject("Project") - + from PyInstallerInterface.PyInstallerCleanupDialog import ( - PyInstallerCleanupDialog + PyInstallerCleanupDialog, ) + dlg = PyInstallerCleanupDialog() if dlg.exec() == QDialog.DialogCode.Accepted: removeDirs = dlg.getDirectories() @@ -505,14 +540,15 @@ def installDependencies(pipInstall): """ Function to install dependencies of this plug-in. - + @param pipInstall function to be called with a list of package names. @type function """ try: - import PyInstaller # __IGNORE_WARNING__ + import PyInstaller # __IGNORE_WARNING__ except ImportError: pipInstall(["pyinstaller"]) + # # eflag: noqa = M801