PluginCxFreeze.py

Tue, 23 Jun 2020 19:23:24 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 23 Jun 2020 19:23:24 +0200
changeset 125
13f16f4e355a
parent 123
dd6f7c9041a4
child 127
a76379d552f2
permissions
-rw-r--r--

Removed support for Python2.

# -*- coding: utf-8 -*-

# Copyright (c) 2010 - 2020 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the CxFreeze plugin.
"""

import os
import platform

from PyQt5.QtCore import QObject, QTranslator, QCoreApplication, QProcess
from PyQt5.QtWidgets import QDialog

from E5Gui import E5MessageBox
from E5Gui.E5Action import E5Action
from E5Gui.E5Application import e5App
    
import Utilities

# Start-of-Header
name = "CxFreeze Plugin"
author = "Detlev Offenbach <detlev@die-offenbachs.de>"
autoactivate = True
deactivateable = True
version = "7.0.0"
className = "CxFreezePlugin"
packageName = "CxFreeze"
shortDescription = "Show the CxFreeze dialogs."
longDescription = (
    """This plugin implements the CxFreeze dialogs."""
    """ CxFreeze is used to generate a distribution package."""
)
needsRestart = False
pyqtApi = 2
# End-of-Header

error = ""

exePy3 = []


def exeDisplayDataList():
    """
    Public method to support the display of some executable info.
    
    @return dictionary containing the data to query the presence of
        the executable
    """
    dataList = []
    data = {
        "programEntry": True,
        "header": QCoreApplication.translate(
            "CxFreezePlugin", "Packagers - cx_freeze"),
        "exe": 'dummyfreeze',
        "versionCommand": '--version',
        "versionStartsWith": 'dummyfreeze',
        "versionPosition": -1,
        "version": "",
        "versionCleanup": None,
    }
    
    if _checkProgram():
        for exePath in exePy3:
            data["exe"] = exePath
            data["versionStartsWith"] = "cxfreeze"
            dataList.append(data.copy())
    else:
        dataList.append(data)
    return dataList


def _findExecutable(majorVersion):
    """
    Restricted function to determine the names of the executable.
    
    @param majorVersion major python version of the executables (int)
    @return names of the executable (list)
    """
    # Determine Python Version
    if majorVersion == 3:
        minorVersions = range(10)
    else:
        return []
    
    executables = set()
    if Utilities.isWindowsPlatform():
        #
        # Windows
        #
        try:
            import winreg
        except ImportError:
            import _winreg as winreg    # __IGNORE_WARNING__
        
        def getExePath(branch, access, versionStr):
            try:
                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')
                exe = os.path.join(installpath, 'Scripts', 'cxfreeze.bat')
                if os.access(exe, os.X_OK):
                    return exe
            except WindowsError:        # __IGNORE_WARNING__
                return None
            return None
        
        versionSuffixes = ["", "-32", "-64"]
        for minorVersion in minorVersions:
            for versionSuffix in versionSuffixes:
                versionStr = '{0}.{1}{2}'.format(majorVersion, minorVersion,
                                                 versionSuffix)
                exePath = getExePath(
                    winreg.HKEY_CURRENT_USER,
                    winreg.KEY_WOW64_32KEY | winreg.KEY_READ, versionStr)
                if exePath is not None:
                    executables.add(exePath)
                
                exePath = getExePath(
                    winreg.HKEY_LOCAL_MACHINE,
                    winreg.KEY_WOW64_32KEY | winreg.KEY_READ, versionStr)
                if exePath is not None:
                    executables.add(exePath)
                
                # Even on Intel 64-bit machines it's 'AMD64'
                if platform.machine() == 'AMD64':
                    exePath = getExePath(
                        winreg.HKEY_CURRENT_USER,
                        winreg.KEY_WOW64_64KEY | winreg.KEY_READ, versionStr)
                    if exePath is not None:
                        executables.add(exePath)
                    
                    exePath = getExePath(
                        winreg.HKEY_LOCAL_MACHINE,
                        winreg.KEY_WOW64_64KEY | winreg.KEY_READ, versionStr)
                    if exePath is not None:
                        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')
            if path:
                dirs = path.split(os.pathsep)
                for directory in dirs:
                    for suffix in (".bat", ".exe"):
                        exe = os.path.join(directory, "cxfreeze" + suffix)
                        if os.access(exe, os.X_OK):
                            executables.add(exe)
    else:
        #
        # Linux, Unix ...
        cxfreezeScript = 'cxfreeze'
        scriptSuffixes = ["", "-python{0}".format(majorVersion)]
        for minorVersion in minorVersions:
            scriptSuffixes.append(
                "-python{0}.{1}".format(majorVersion, minorVersion))
        # There could be multiple cxfreeze executables in the path
        # e.g. for different python variants
        path = Utilities.getEnvironmentEntry('PATH')
        # environment variable not defined
        if path is None:
            return []
        
        # step 1: determine possible candidates
        exes = []
        dirs = path.split(os.pathsep)
        for directory in dirs:
            for suffix in scriptSuffixes:
                exe = os.path.join(directory, cxfreezeScript + suffix)
                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])"]
        for exe in exes:
            try:
                f = open(exe, "r")
                line0 = f.readline()
                program = line0.replace("#!", "").strip()
                process = QProcess()
                process.start(program, versionArgs)
                process.waitForFinished(5000)
                # get a QByteArray of the output
                versionBytes = process.readAllStandardOutput()
                versionStr = str(versionBytes, encoding='utf-8').strip()
                if versionStr == "3":
                    _exePy3.add(exe)
            finally:
                f.close()
        
        executables = _exePy3
    
    # sort items, the probably newest topmost
    executables = list(executables)
    executables.sort(reverse=True)
    return executables


def _checkProgram():
    """
    Restricted function to check the availability of cxfreeze.
    
    @return flag indicating availability (boolean)
    """
    global error, exePy3
    
    exePy3 = _findExecutable(3)
    if exePy3 == []:
        if Utilities.isWindowsPlatform():
            error = QCoreApplication.translate(
                "CxFreezePlugin",
                "The cxfreeze.bat executable could not be found."
                "Did you run the cxfreeze-postinstall script?")
        else:
            error = QCoreApplication.translate(
                "CxFreezePlugin",
                "The cxfreeze executable could not be found.")
        return False
    else:
        return True


class CxFreezePlugin(QObject):
    """
    Class implementing the CxFreeze plugin.
    """
    def __init__(self, ui):
        """
        Constructor
        
        @param ui reference to the user interface object (UI.UserInterface)
        """
        QObject.__init__(self, ui)
        self.__ui = ui
        self.__initialize()
        _checkProgram()
        
        self.__translator = None
        self.__loadTranslator()
        
    def __initialize(self):
        """
        Private slot to (re)initialize the plugin.
        """
        self.__projectAct = None
        self.__projectSeparator = None
       
    def activate(self):
        """
        Public method to activate this plugin.
        
        @return tuple of None and activation status (boolean)
        """
        global error
        
        # There is already an error, don't activate
        if error:
            return None, False
        
        # cxfreeze is only activated if it is available
        if not _checkProgram():
            return None, False
        
        project = e5App().getObject("Project")
        menu = project.getMenu("Packagers")
        if menu:
            self.__projectAct = E5Action(
                self.tr('Use cx_freeze'),
                self.tr('Use cx_&freeze'), 0, 0,
                self, 'packagers_cxfreeze')
            self.__projectAct.setStatusTip(
                self.tr('Generate a distribution package using cx_freeze'))
            self.__projectAct.setWhatsThis(self.tr(
                """<b>Use cx_freeze</b>"""
                """<p>Generate a distribution package using cx_freeze."""
                """ The command is executed in the project path. All"""
                """ files and directories must be given absolute or"""
                """ relative to the project directory.</p>"""
            ))
            self.__projectAct.triggered.connect(self.__cxfreeze)
            project.addE5Actions([self.__projectAct])
            self.__projectSeparator = menu.addSeparator()
            menu.addAction(self.__projectAct)
            project.showMenu.connect(self.__projectShowMenu)
        
        error = ""
        return None, True

    def deactivate(self):
        """
        Public method to deactivate this plugin.
        """
        menu = e5App().getObject("Project").getMenu("Packagers")
        if menu:
            if self.__projectAct:
                menu.removeAction(self.__projectAct)
                e5App().getObject("Project").removeE5Actions(
                    [self.__projectAct])
            if self.__projectSeparator:
                menu.removeAction(self.__projectSeparator)
        
        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 (string)
        @param menu reference to the menu (QMenu)
        """
        if menuName == "Packagers":
            if self.__projectAct is not None:
                self.__projectAct.setEnabled(
                    e5App().getObject("Project").getProjectLanguage() ==
                    "Python3"
                )
    
    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__),
                                          "CxFreeze", "i18n")
                translation = "cxfreeze_{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.")
    
    def __cxfreeze(self):
        """
        Private slot to handle the cxfreeze execution.
        """
        project = e5App().getObject("Project")
        if not project.getMainScript():
            # no main script defined
            E5MessageBox.critical(
                self.__ui,
                self.tr("cxfreeze"),
                self.tr(
                    """There is no main script defined for the current"""
                    """ project."""),
                E5MessageBox.StandardButtons(E5MessageBox.Abort))
            return
        
        majorVersionStr = project.getProjectLanguage()
        exe = {"Python3": exePy3}.get(majorVersionStr)
        if exe == []:
            E5MessageBox.critical(
                self.__ui,
                self.tr("cxfreeze"),
                self.tr("""The cxfreeze executable could not be found."""))
            return

        # check if all files saved and errorfree before continue
        if not project.checkAllScriptsDirty(reportSyntaxErrors=True):
            return

        from CxFreeze.CxfreezeConfigDialog import CxfreezeConfigDialog
        parms = project.getData('PACKAGERSPARMS', "CXFREEZE")
        dlg = CxfreezeConfigDialog(project, exe, parms)
        if dlg.exec_() == QDialog.Accepted:
            args, parms = dlg.generateParameters()
            project.setData('PACKAGERSPARMS', "CXFREEZE", parms)
            
            # now do the call
            from CxFreeze.CxfreezeExecDialog import CxfreezeExecDialog
            dia = CxfreezeExecDialog("cxfreeze")
            dia.show()
            res = dia.start(args, parms, project.ppath,
                            project.getMainScript())
            if res:
                dia.exec_()

#
# eflag: noqa = M801

eric ide

mercurial