PyInstaller/PyInstallerConfigDialog.py

Mon, 04 May 2020 18:15:28 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 04 May 2020 18:15:28 +0200
changeset 27
25ff8953e335
parent 26
e4135114d483
child 28
3c8dbc198753
permissions
-rw-r--r--

Added code to search the executable in virtual environment as well on Windows platforms.

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

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

"""
Module implementing PyInstallerConfigDialog.
"""

from __future__ import unicode_literals

import copy

from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QDialog, QDialogButtonBox

from E5Gui.E5PathPicker import E5PathPickerModes

from .Ui_PyInstallerConfigDialog import Ui_PyInstallerConfigDialog

import Globals


class PyInstallerConfigDialog(QDialog, Ui_PyInstallerConfigDialog):
    """
    Class implementing a dialog to enter the parameters for pyinstaller
    and pyi-makespec.
    """
    def __init__(self, project, executables, params=None, mode="installer",
                 parent=None):
        """
        Constructor
        
        @param project reference to the project object
        @type Project.Project
        @param executables names of the pyinstaller executables
        @type list of str
        @param params parameters to set in the dialog
        @type dict
        @param mode mode of the dialog
        @type str (one of 'installer' or 'spec')
        @param parent reference to the parent widget
        @type QWidget
        """
        assert mode in ("installer", "spec")
        
        super(PyInstallerConfigDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.__project = project
        self.__mode = mode
        
        self.inputFilePicker.setMode(E5PathPickerModes.OpenFileMode)
        self.inputFilePicker.setDefaultDirectory(
            self.__project.getProjectPath())
        if self.__mode == "installer":
            self.inputFilePicker.setFilters(self.tr(
                "Python Files (*.py *.py2 *.py3);;"
                "Python GUI Files (*.pyw *.pyw2 *.pyw3);;"
                "Spec Files (*.spec);;"
                "All Files (*)"
            ))
        elif self.__mode == "spec":
            self.inputFilePicker.setFilters(self.tr(
                "Python Files (*.py *.py2 *.py3);;"
                "Python GUI Files (*.pyw *.pyw2 *.pyw3);;"
                "All Files (*)"
            ))
        
        self.executableCombo.addItems(executables)
        
        if not bool(project.getMainScript()):
            # no main script defined
            self.selectedScriptButton.setChecked(True)
            self.mainScriptButton.setEnabled(False)
        
        self.iconFilePicker.setMode(E5PathPickerModes.OpenFileMode)
        self.iconFilePicker.setDefaultDirectory(
            self.__project.getProjectPath())
        if Globals.isMacPlatform():
            self.iconFilePicker.setFilters(self.tr(
                "Icon Files (*.icns);;"
                "All Files (*)"
            ))
        elif Globals.isWindowsPlatform():
            self.iconFilePicker.setFilters(self.tr(
                "Icon Files (*.ico);;"
                "Executable Files (*.exe);;"
                "All Files (*)"
            ))
        
        # disable platform specific tabs
        self.tabWidget.setTabEnabled(
            self.tabWidget.indexOf(self.windowsMacTab),
            Globals.isMacPlatform() or Globals.isWindowsPlatform())
        self.tabWidget.setTabEnabled(
            self.tabWidget.indexOf(self.macTab),
            Globals.isMacPlatform())
        
        self.__initializeDefaults()
        
        # get a copy of the defaults to store the user settings
        self.__parameters = copy.deepcopy(self.__defaults)
        
        # combine it with the values of params
        if params is not None:
            self.__parameters.update(params)
        
        # initialize general tab
        if mode == "installer" and bool(self.__parameters["pyinstaller"]):
            self.executableCombo.setCurrentIndex(
                self.executableCombo.findText(
                    self.__parameters["pyinstaller"]))
        elif mode == "spec" and bool(self.__parameters["pyi-makespec"]):
            self.executableCombo.setCurrentIndex(
                self.executableCombo.findText(
                    self.__parameters["pyi-makespec"]))
        if self.__parameters["mainscript"]:
            self.mainScriptButton.setChecked(True)
        else:
            self.selectedScriptButton.setChecked(True)
        self.inputFilePicker.setText(self.__parameters["inputFile"])
        if self.__parameters["oneDirectory"]:
            self.oneDirButton.setChecked(True)
        else:
            self.oneFileButton.setChecked(True)
        self.nameEdit.setText(self.__parameters["name"])
        self.keyEdit.setText(self.__parameters["encryptionKey"])
        self.cleanCheckBox.setChecked(self.__parameters["cleanBeforeBuilding"])
        
        # initialize Windows and macOS tab
        if self.__parameters["consoleApplication"]:
            self.consoleButton.setChecked(True)
        else:
            self.windowedButton.setChecked(True)
        self.iconFilePicker.setText(self.__parameters["iconFile"])
        self.iconIdEdit.setText(self.__parameters["iconId"])
        
        # initialize maxOS specific tab
        self.bundleIdentifierEdit.setText(
            self.__parameters["bundleIdentifier"])
        
        self.__updateOkButton()
        
        msh = self.minimumSizeHint()
        self.resize(max(self.width(), msh.width()), msh.height())
    
    def __initializeDefaults(self):
        """
        Private method to set the default values.
        
        These are needed later on to generate the command line parameters.
        """
        self.__defaults = {
            # general options
            "pyinstaller": "",
            "pyi-makespec": "",
            "mainscript": bool(self.__project.getMainScript()),
            "inputFile": "",
            "oneDirectory": True,
            "name": "",
            "encryptionKey": "",
            "cleanBeforeBuilding": False,
            
            # Windows and macOS options
            "consoleApplication": True,
            "iconFile": "",
            "iconId": "",
            
            # macOS specific options
            "bundleIdentifier": "",
        }
    
    def generateParameters(self):
        """
        Public method that generates the command line parameters.
        
        It generates a list of strings to be used to set the QProcess arguments
        for the pyinstaller/pyi-makespec call and a list containing the non
        default parameters. The second list can be passed back upon object
        generation to overwrite the default settings.
        
        @return a tuple of the command line parameters, non default parameters
            and the script path
        @rtype tuple of (list of str, dict, str)
        """
        parms = {}
        args = []
        
        # 1. the program name
        if self.__mode == "installer":
            args.append(self.__parameters["pyinstaller"])
            parms["pyinstaller"] = self.__parameters["pyinstaller"]
        elif self.__mode == "spec":
            args.append(self.__parameters["pyi-makespec"])
            parms["pyi-makespec"] = self.__parameters["pyi-makespec"]
        
        # 2. the commandline options
        # 2.1 general options, input
        if not self.__parameters["mainscript"]:
            parms["mainscript"] = False
            parms["inputFile"] = self.__parameters["inputFile"]
        
        runWithSpec = self.__parameters["inputFile"].endswith(".spec")
        if not runWithSpec:
            # 2.2 general options, part 1
            if not self.__parameters["oneDirectory"]:
                parms["oneDirectory"] = self.__parameters["oneDirectory"]
                args.append("--onefile")
            if self.__parameters["name"] != self.__defaults["name"]:
                parms["name"] = self.__parameters["name"]
                args.append("--name")
                args.append(self.__parameters["name"])
            if (
                self.__parameters["encryptionKey"] !=
                self.__defaults["encryptionKey"]
            ):
                parms["encryptionKey"] = self.__parameters["encryptionKey"]
                args.append("--key")
                args.append(self.__parameters["encryptionKey"])
        
            # 2.3 Windows and macOS options
            if (
                self.__parameters["consoleApplication"] !=
                self.__defaults["consoleApplication"]
            ):
                parms["consoleApplication"] = (
                    self.__parameters["consoleApplication"]
                )
                args.append("--windowed")
            if self.__parameters["iconFile"] != self.__defaults["iconFile"]:
                parms["iconFile"] = self.__parameters["iconFile"]
                parms["iconId"] = self.__parameters["iconId"]
                args.append("--icon")
                if self.__parameters["iconFile"].endswith(".exe"):
                    if bool(self.__parameters["iconId"]):
                        iconId = self.__parameters["iconId"]
                    else:
                        iconId = "0"
                    args.append("{0},{1}".format(
                        self.__parameters["iconFile"], iconId))
                else:
                    args.append(self.__parameters["iconFile"])
        
            # 2.4 macOS specific options
            if (
                self.__parameters["bundleIdentifier"] !=
                self.__defaults["bundleIdentifier"]
            ):
                parms["bundleIdentifier"] = (
                    self.__parameters["bundleIdentifier"]
                )
                args.append("--osx-bundle-identifier")
                args.append(self.__parameters["bundleIdentifier"])
        
        # 2.5 general options, part 2
        if (
            self.__parameters["cleanBeforeBuilding"] !=
            self.__defaults["cleanBeforeBuilding"]
        ):
            parms["cleanBeforeBuilding"] = (
                self.__parameters["cleanBeforeBuilding"]
            )
            args.append("--clean")
        
        # 3. always add these arguments
        if self.__mode == "installer":
            args.append("--noconfirm")  # don't ask the user
        
        # determine the script to be processed
        if self.__parameters["mainscript"]:
            script = self.__project.getMainScript()
        else:
            script = self.__parameters["inputFile"]
        
        return args, parms, script

    def accept(self):
        """
        Public method called by the Ok button.
        
        It saves the values in the parameters dictionary.
        """
        # get data of general tab
        if self.__mode == "installer":
            self.__parameters["pyinstaller"] = (
                self.executableCombo.currentText()
            )
        elif self.__mode == "spec":
            self.__parameters["pyi-makespec"] = (
                self.executableCombo.currentText()
            )
        self.__parameters["mainscript"] = self.mainScriptButton.isChecked()
        self.__parameters["inputFile"] = self.inputFilePicker.text()
        self.__parameters["oneDirectory"] = self.oneDirButton.isChecked()
        self.__parameters["name"] = self.nameEdit.text()
        self.__parameters["encryptionKey"] = self.keyEdit.text()
        self.__parameters["cleanBeforeBuilding"] = (
            self.cleanCheckBox.isChecked()
        )
        
        # get data of Windows and macOS tab
        self.__parameters["consoleApplication"] = (
            self.consoleButton.isChecked()
        )
        self.__parameters["iconFile"] = self.iconFilePicker.text()
        self.__parameters["iconId"] = self.iconIdEdit.text()
        
        # get data of macOS specific tab
        self.__parameters["bundleIdentifier"] = (
            self.bundleIdentifierEdit.text()
        )

        # call the accept slot of the base class
        super(PyInstallerConfigDialog, self).accept()
    
    def __updateOkButton(self):
        """
        Private method to update the enabled state of the OK button.
        """
        enable = True
        
        # If not to be run with the project main script, a script or
        # spec file must be selected.
        if (
            self.selectedScriptButton.isChecked() and
            not bool(self.inputFilePicker.text())
        ):
            enable = False
        
        # If the icon shall be picked from a .exe file, an icon ID
        # must be entered (Windows only).
        if (
            self.iconFilePicker.text().endswith(".exe") and
            not bool(self.iconIdEdit.text())
        ):
            enable = False
        
        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable)
    
    @pyqtSlot(bool)
    def on_selectedScriptButton_toggled(self, checked):
        """
        Private slot to handle changes of the radio button state.
        
        @param checked state of the radio button
        @type bool
        """
        self.__updateOkButton()
    
    @pyqtSlot(str)
    def on_inputFilePicker_textChanged(self, txt):
        """
        Private slot to handle changes of the input file.
        
        @param txt text of the file edit
        @type str
        """
        self.__updateOkButton()
    
    @pyqtSlot(str)
    def on_iconFilePicker_textChanged(self, txt):
        """
        Private slot to handle changes of the icon file.
        
        @param txt text of the file edit
        @type str
        """
        self.iconIdEdit.setEnabled(txt.endswith(".exe"))
        self.__updateOkButton()
    
    @pyqtSlot(str)
    def on_iconIdEdit_textChanged(self, txt):
        """
        Private slot to handle changes of the icon ID.
        
        @param txt iconID
        @type str
        """
        self.__updateOkButton()

eric ide

mercurial