Continued implementing the PyInstaller interface (finished the config dialog).

Thu, 18 Jan 2018 14:30:06 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 18 Jan 2018 14:30:06 +0100
changeset 4
52f0572b5908
parent 3
eb2d30b4d34e
child 5
8c92d66d20e4

Continued implementing the PyInstaller interface (finished the config dialog).

PluginPyInstaller.e4p file | annotate | diff | comparison | revisions
PluginPyInstaller.py file | annotate | diff | comparison | revisions
PyInstaller/PyInstallerCleanupDialog.py file | annotate | diff | comparison | revisions
PyInstaller/PyInstallerConfigDialog.py file | annotate | diff | comparison | revisions
PyInstaller/PyInstallerConfigDialog.ui file | annotate | diff | comparison | revisions
--- a/PluginPyInstaller.e4p	Wed Jan 17 16:25:59 2018 +0100
+++ b/PluginPyInstaller.e4p	Thu Jan 18 14:30:06 2018 +0100
@@ -153,4 +153,156 @@
     <FiletypeAssociation pattern="README.*" type="OTHERS"/>
     <FiletypeAssociation pattern="Ui_*.py" type="__IGNORE__"/>
   </FiletypeAssociations>
+  <Checkers>
+    <CheckersParams>
+      <dict>
+        <key>
+          <string>Pep8Checker</string>
+        </key>
+        <value>
+          <dict>
+            <key>
+              <string>BuiltinsChecker</string>
+            </key>
+            <value>
+              <dict>
+                <key>
+                  <string>bytes</string>
+                </key>
+                <value>
+                  <list>
+                    <string>unicode</string>
+                  </list>
+                </value>
+                <key>
+                  <string>chr</string>
+                </key>
+                <value>
+                  <list>
+                    <string>unichr</string>
+                  </list>
+                </value>
+                <key>
+                  <string>str</string>
+                </key>
+                <value>
+                  <list>
+                    <string>unicode</string>
+                  </list>
+                </value>
+              </dict>
+            </value>
+            <key>
+              <string>CopyrightAuthor</string>
+            </key>
+            <value>
+              <string></string>
+            </value>
+            <key>
+              <string>CopyrightMinFileSize</string>
+            </key>
+            <value>
+              <int>0</int>
+            </value>
+            <key>
+              <string>DocstringType</string>
+            </key>
+            <value>
+              <string>eric</string>
+            </value>
+            <key>
+              <string>ExcludeFiles</string>
+            </key>
+            <value>
+              <string>*/Ui_*.py, */*_rc.py,</string>
+            </value>
+            <key>
+              <string>ExcludeMessages</string>
+            </key>
+            <value>
+              <string>C101, E265, E266, E305, E402, M811, N802, N803, N807, N808, N821, W293</string>
+            </value>
+            <key>
+              <string>FixCodes</string>
+            </key>
+            <value>
+              <string></string>
+            </value>
+            <key>
+              <string>FixIssues</string>
+            </key>
+            <value>
+              <bool>False</bool>
+            </value>
+            <key>
+              <string>FutureChecker</string>
+            </key>
+            <value>
+              <string>unicode_literals</string>
+            </value>
+            <key>
+              <string>HangClosing</string>
+            </key>
+            <value>
+              <bool>False</bool>
+            </value>
+            <key>
+              <string>IncludeMessages</string>
+            </key>
+            <value>
+              <string></string>
+            </value>
+            <key>
+              <string>LineComplexity</string>
+            </key>
+            <value>
+              <int>20</int>
+            </value>
+            <key>
+              <string>LineComplexityScore</string>
+            </key>
+            <value>
+              <int>10</int>
+            </value>
+            <key>
+              <string>MaxCodeComplexity</string>
+            </key>
+            <value>
+              <int>10</int>
+            </value>
+            <key>
+              <string>MaxLineLength</string>
+            </key>
+            <value>
+              <int>79</int>
+            </value>
+            <key>
+              <string>NoFixCodes</string>
+            </key>
+            <value>
+              <string>E501</string>
+            </value>
+            <key>
+              <string>RepeatMessages</string>
+            </key>
+            <value>
+              <bool>True</bool>
+            </value>
+            <key>
+              <string>ShowIgnored</string>
+            </key>
+            <value>
+              <bool>False</bool>
+            </value>
+            <key>
+              <string>ValidEncodings</string>
+            </key>
+            <value>
+              <string>latin-1, utf-8</string>
+            </value>
+          </dict>
+        </value>
+      </dict>
+    </CheckersParams>
+  </Checkers>
 </Project>
--- a/PluginPyInstaller.py	Wed Jan 17 16:25:59 2018 +0100
+++ b/PluginPyInstaller.py	Thu Jan 18 14:30:06 2018 +0100
@@ -130,7 +130,7 @@
         for minorVersion in minorVersions:
             for versionSuffix in versionSuffixes:
                 versionStr = '{0}.{1}{2}'.format(majorVersion, minorVersion,
-                                                  versionSuffix)
+                                                 versionSuffix)
                 exePaths = getExePath(
                     winreg.HKEY_CURRENT_USER,
                     winreg.KEY_WOW64_32KEY | winreg.KEY_READ, versionStr)
@@ -399,10 +399,10 @@
         project = e5App().getObject("Project")
         majorVersionStr = project.getProjectLanguage()
         if majorVersionStr == "Python3":
-            executables = [f for f in exePy3 if 
+            executables = [f for f in exePy3 if
                            f.endswith(("pyinstaller", "pyinstaller.exe"))]
         else:
-            executables = [f for f in exePy2 if 
+            executables = [f for f in exePy2 if
                            f.endswith(("pyinstaller", "pyinstaller.exe"))]
         if not executables:
             E5MessageBox.critical(
@@ -423,6 +423,9 @@
             args, params = dlg.generateParameters()
             project.setData('PACKAGERSPARMS', "PYINSTALLER", params)
             
+            # TODO: implement pyinstaller
+            return
+            
             # now do the call
             from PyInstaller.PyInstallerExecDialog import PyInstallerExecDialog
             dia = PyInstallerExecDialog("pyinstaller")
@@ -431,7 +434,6 @@
                             project.getMainScript())
             if res:
                 dia.exec_()
-        # TODO: implement pyinstaller
     
     @pyqtSlot()
     def __pyiMakeSpec(self):
@@ -442,10 +444,10 @@
         project = e5App().getObject("Project")
         majorVersionStr = project.getProjectLanguage()
         if majorVersionStr == "Python3":
-            executables = [f for f in exePy3 if 
+            executables = [f for f in exePy3 if
                            f.endswith(("pyi-makespec", "pyi-makespec.exe"))]
         else:
-            executables = [f for f in exePy2 if 
+            executables = [f for f in exePy2 if
                            f.endswith(("pyi-makespec", "pyi-makespec.exe"))]
         if not executables:
             E5MessageBox.critical(
--- a/PyInstaller/PyInstallerCleanupDialog.py	Wed Jan 17 16:25:59 2018 +0100
+++ b/PyInstaller/PyInstallerCleanupDialog.py	Thu Jan 18 14:30:06 2018 +0100
@@ -7,6 +7,8 @@
 Module implementing a dialog to select the cleanup action.
 """
 
+from __future__ import unicode_literals
+
 from PyQt5.QtWidgets import QDialog
 
 from .Ui_PyInstallerCleanupDialog import Ui_PyInstallerCleanupDialog
@@ -35,6 +37,9 @@
     def getDirectories(self):
         """
         Public method to get the project relative directories to be cleaned.
+        
+        @return list of directories to be removed
+        @rtype list of str
         """
         dirs = []
         
--- a/PyInstaller/PyInstallerConfigDialog.py	Wed Jan 17 16:25:59 2018 +0100
+++ b/PyInstaller/PyInstallerConfigDialog.py	Thu Jan 18 14:30:06 2018 +0100
@@ -1,11 +1,18 @@
 # -*- coding: utf-8 -*-
 
+# Copyright (c) 2018 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
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox
 
 from E5Gui.E5PathPicker import E5PathPickerModes
 
@@ -16,7 +23,8 @@
 
 class PyInstallerConfigDialog(QDialog, Ui_PyInstallerConfigDialog):
     """
-    Class documentation goes here.
+    Class implementing a dialog to enter the parameters for pyinstaller
+    and pyi-makespec.
     """
     def __init__(self, project, executables, params=None, mode="installer",
                  parent=None):
@@ -39,23 +47,36 @@
         super(PyInstallerConfigDialog, self).__init__(parent)
         self.setupUi(self)
         
+        self.__project = project
+        self.__mode = mode
+        
         self.inputFilePicker.setMode(E5PathPickerModes.OpenFileMode)
-        self.inputFilePicker.setFilters(self.tr(
-            "Python Files (*.py *.py2 *.py3);;"
-            "Python GUI Files (*.pyw *.pyw2 *.pyw3);;"
-            "Spec Files (*.spec);;"
-            "All Files (*)"
-        ))
+        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)
         
-        self.__project = project
-        if project.getMainScript() == "":
+        if not bool(project.getMainScript()):
             # no main script defined
-            self.selectedScriptButton.setChecke(True)
+            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);;"
@@ -68,9 +89,273 @@
                 "All Files (*)"
             ))
         
+        # disable platform specific tabs
         self.tabWidget.setTabEnabled(
-            1,
+            self.tabWidget.indexOf(self.windowsMacTab),
             Globals.isMacPlatform() or Globals.isWindowsPlatform())
         self.tabWidget.setTabEnabled(
-            2,
+            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:
+            for key, value in params.items():
+                if key in self.__parameters:
+                    self.__parameters[key] = params[key]
+        
+        # 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 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 and non default
+            parameters
+        @rtype tuple of (list of str, dict)
+        """
+        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
+        args.append("--noconfirm")  # don't ask the user
+        
+        # finalize the arguments array
+        if self.__parameters["mainscript"]:
+            args.append(self.__project.getMainScript())
+        else:
+            args.append(self.__parameters["inputFile"])
+        
+        return args, parms
+
+    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()
--- a/PyInstaller/PyInstallerConfigDialog.ui	Wed Jan 17 16:25:59 2018 +0100
+++ b/PyInstaller/PyInstallerConfigDialog.ui	Thu Jan 18 14:30:06 2018 +0100
@@ -198,9 +198,6 @@
           </item>
           <item row="0" column="1">
            <widget class="E5PathPicker" name="iconFilePicker" native="true">
-            <property name="enabled">
-             <bool>false</bool>
-            </property>
             <property name="sizePolicy">
              <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
               <horstretch>0</horstretch>
@@ -265,7 +262,7 @@
           </widget>
          </item>
          <item>
-          <widget class="E5ClearableLineEdit" name="BundleIdentifierEdit">
+          <widget class="E5ClearableLineEdit" name="bundleIdentifierEdit">
            <property name="toolTip">
             <string>Enter the macOS app bundle identifier</string>
            </property>
@@ -330,7 +327,7 @@
   <tabstop>windowedButton</tabstop>
   <tabstop>iconFilePicker</tabstop>
   <tabstop>iconIdEdit</tabstop>
-  <tabstop>BundleIdentifierEdit</tabstop>
+  <tabstop>bundleIdentifierEdit</tabstop>
  </tabstops>
  <resources/>
  <connections>

eric ide

mercurial