setup.py: continued implementing support for setup.py. setup.py

Tue, 16 Apr 2019 19:43:53 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 16 Apr 2019 19:43:53 +0200
branch
setup.py
changeset 6950
62e39a353cd9
parent 6949
a5255f1ba3f0
child 6951
ef3e87580dc9

setup.py: continued implementing support for setup.py.

.hgignore file | annotate | diff | comparison | revisions
MANIFEST.in file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
eric6/eric6_post_install.py file | annotate | diff | comparison | revisions
setup.py file | annotate | diff | comparison | revisions
--- a/.hgignore	Mon Apr 15 19:53:29 2019 +0200
+++ b/.hgignore	Tue Apr 16 19:43:53 2019 +0200
@@ -21,4 +21,3 @@
 glob:eric6.egg-info
 glob:dist
 glob:build
-glob:VERSION
--- a/MANIFEST.in	Mon Apr 15 19:53:29 2019 +0200
+++ b/MANIFEST.in	Tue Apr 16 19:43:53 2019 +0200
@@ -6,4 +6,3 @@
 include MANIFEST.in
 include eric6.e4p
 include setup.py
-include VERSION
--- a/eric6.e4p	Mon Apr 15 19:53:29 2019 +0200
+++ b/eric6.e4p	Tue Apr 16 19:43:53 2019 +0200
@@ -1698,6 +1698,7 @@
     <Source>eric6/eric6_pluginrepository.pyw</Source>
     <Source>eric6/eric6_pluginuninstall.py</Source>
     <Source>eric6/eric6_pluginuninstall.pyw</Source>
+    <Source>eric6/eric6_post_install.py</Source>
     <Source>eric6/eric6_qregexp.py</Source>
     <Source>eric6/eric6_qregexp.pyw</Source>
     <Source>eric6/eric6_qregularexpression.py</Source>
@@ -2283,14 +2284,14 @@
     <Other>docs/THANKS</Other>
     <Other>docs/changelog</Other>
     <Other>eric6.e4p</Other>
-    <Other>eric6/APIs/Python/zope-2.10.7.api</Other>
-    <Other>eric6/APIs/Python/zope-2.11.2.api</Other>
-    <Other>eric6/APIs/Python/zope-3.3.1.api</Other>
     <Other>eric6/APIs/Python3/PyQt4.bas</Other>
     <Other>eric6/APIs/Python3/PyQt5.bas</Other>
     <Other>eric6/APIs/Python3/QScintilla2.bas</Other>
     <Other>eric6/APIs/Python3/eric6.api</Other>
     <Other>eric6/APIs/Python3/eric6.bas</Other>
+    <Other>eric6/APIs/Python/zope-2.10.7.api</Other>
+    <Other>eric6/APIs/Python/zope-2.11.2.api</Other>
+    <Other>eric6/APIs/Python/zope-3.3.1.api</Other>
     <Other>eric6/APIs/QSS/qss.api</Other>
     <Other>eric6/APIs/Ruby/Ruby-1.8.7.api</Other>
     <Other>eric6/APIs/Ruby/Ruby-1.8.7.bas</Other>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/eric6_post_install.py	Tue Apr 16 19:43:53 2019 +0200
@@ -0,0 +1,201 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+from __future__ import unicode_literals
+
+import sys
+import os
+import shutil
+import sysconfig
+
+######################################################################
+## Post installation hooks for Windows below
+######################################################################
+
+def createWindowsLinks():
+    """
+    Create Desktop and Start Menu links.
+    """
+    regPath = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer" + \
+              "\\User Shell Folders"
+    
+    # 1. create desktop shortcuts
+    regName = "Desktop"
+    desktopFolder = os.path.normpath(
+        os.path.expandvars(getWinregEntry(regName, regPath)))
+    for linkName, targetPath, iconPath in windowsDesktopEntries():
+        linkPath = os.path.join(desktopFolder, linkName)
+        createWindowsShortcut(linkPath, targetPath, iconPath)
+    
+    # 2. create start menu entry and shortcuts
+    regName = "Programs"
+    programsEntry = getWinregEntry(regName, regPath)
+    if programsEntry:
+        programsFolder = os.path.normpath(os.path.expandvars(programsEntry))
+        eric6EntryPath = os.path.join(programsFolder, windowsProgramsEntry())
+        if not os.path.exists(eric6EntryPath):
+            try:
+                os.makedirs(eric6EntryPath)
+            except EnvironmentError:
+                # maybe restrictions prohibited link creation
+                return
+        
+        for linkName, targetPath, iconPath in windowsDesktopEntries():
+            linkPath = os.path.join(eric6EntryPath, linkName)
+            createWindowsShortcut(linkPath, targetPath, iconPath)
+
+
+def getWinregEntry(name, path):
+    """
+    Function to get an entry from the Windows Registry.
+    
+    @param name variable name
+    @type str
+    @param path registry path of the variable
+    @type str
+    @return value of requested registry variable
+    @rtype any
+    """
+    try:
+        import _winreg as winreg
+    except ImportError:
+        try:
+            import winreg
+        except ImportError:
+            return None
+    
+    try:
+        registryKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0,
+                                     winreg.KEY_READ)
+        value, _ = winreg.QueryValueEx(registryKey, name)
+        winreg.CloseKey(registryKey)
+        return value
+    except WindowsError:
+        return None
+
+
+def windowsDesktopEntries():
+    """
+    Function to generate data for the Windows Desktop links.
+    
+    @return list of tuples containing the desktop link name,
+        the link target and the icon target
+    @rtype list of tuples of (str, str, str)
+    """
+    majorVersion, minorVersion = sys.version_info[:2]
+    scriptsDir = sysconfig.get_path("scripts")
+    entriesTemplates = [
+        ("eric6 (Python {0}.{1}).lnk",
+         os.path.join(scriptsDir, "eric6.exe"),
+         os.path.join(scriptsDir, "eric6.ico")
+         ),
+        ("eric6 Browser (Python {0}.{1}).lnk",
+         os.path.join(scriptsDir, "eric6_browser.exe"),
+         os.path.join(scriptsDir, "ericWeb48.ico")
+         ),
+    ]
+    
+    return [
+        (e[0].format(majorVersion, minorVersion), e[1], e[2])
+        for e in entriesTemplates
+    ]
+
+
+def createWindowsShortcut(linkPath, targetPath, iconPath):
+    """
+    Create Windows shortcut.
+    
+    @param linkPath path of the shortcut file
+    @type str
+    @param targetPath path the shortcut shall point to
+    @type str
+    @param iconPath path of the icon file
+    @type str
+    """
+    from win32com.client import Dispatch
+    from pywintypes import com_error
+    
+    try:
+        shell = Dispatch('WScript.Shell')
+        shortcut = shell.CreateShortCut(linkPath)
+        shortcut.Targetpath = targetPath
+        shortcut.WorkingDirectory = os.path.dirname(targetPath)
+        shortcut.IconLocation = iconPath
+        shortcut.save()
+    except com_error:
+        # maybe restrictions prohibited link creation
+        pass
+
+
+def windowsProgramsEntry():
+    """
+    Function to generate the name of the Start Menu top entry.
+    
+    @return name of the Start Menu top entry
+    @rtype str
+    """
+    majorVersion, minorVersion = sys.version_info[:2]
+    return "eric6 (Python {0}.{1})".format(majorVersion, minorVersion)
+
+######################################################################
+## Post installation hooks for Linux below
+######################################################################
+
+def copyLinuxMetaData():
+    """
+    Function to copy the meta data files.
+    """
+    # TODO: .desktop files need patching of the exec line
+    srcDir = os.path.join(os.path.dirname(sysconfig.get_path("scripts")),
+                          "share")
+    dstDir = os.path.join(os.path.expanduser("~"), ".local", "share")
+    for metaDir in ["applications", "icons", "appdata", "metainfo"]:
+        copyMetaFilesTree(os.path.join(srcDir, metaDir),
+                          os.path.join(dstDir, metaDir))
+
+
+def copyMetaFilesTree(src, dst):
+    """
+    Function to copy the files of a directory tree.
+    
+    @param src name of the source directory
+    @param dst name of the destination directory
+    """
+    try:
+        names = os.listdir(src)
+    except OSError:
+        # ignore missing directories (most probably the i18n directory)
+        return
+    
+    for name in names:
+        srcname = os.path.join(src, name)
+        dstname = os.path.join(dst, name)
+        if not os.path.isdir(dst):
+            os.makedirs(dst)
+        shutil.copy2(srcname, dstname)
+        os.chmod(dstname, 0o644)
+        
+        if os.path.isdir(srcname):
+            copyMetaFilesTree(srcname, dstname)
+
+######################################################################
+## Main script below
+######################################################################
+
+def main():
+    """
+    Main script
+    """
+    if sys.platform.startswith(("win", "cygwin")):
+        createWindowsLinks()
+    elif sys.platform.startswith("linux"):
+        copyLinuxMetaData()
+    
+    sys.exit(0)
+
+
+if __name__ == "__main__":
+    main()
--- a/setup.py	Mon Apr 15 19:53:29 2019 +0200
+++ b/setup.py	Tue Apr 16 19:43:53 2019 +0200
@@ -4,11 +4,17 @@
 # Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
 #
 
+"""
+Module to prepare a distribution package for uploading to PyPI.
+"""
+
 from __future__ import unicode_literals
 
 import os
 import sys
 import subprocess
+import shutil
+import fnmatch
 
 from setuptools import setup, find_packages
 from distutils.command.install_data import install_data
@@ -25,9 +31,15 @@
     @rtype str
     """
     version = "<unknown>"
-    with open(os.path.join(os.path.dirname(__file__), "VERSION"),
-              encoding="ASCII") as f:
-        version = f.read().strip()
+    if sys.argv[-1].startswith(("1", "2")):
+        # assume it is a version info starting with year
+        version = sys.argv[-1]
+        del sys.argv[-1]
+    else:
+        # calculate according our version scheme (year.month)
+        import datetime
+        today = datetime.date.today()
+        version = "{0}.{1}".format(today.year - 2000, today.month)
     return version
 
 
@@ -65,9 +77,10 @@
                 'eric6/icons/default/eric.png',
                 'eric6/icons/default/ericWeb48.png'
             ]),
-            ('share/metainfo', ['linux/eric6.appdata.xml'])
+            ('share/appdata', ['linux/eric6.appdata.xml']),
+            ('share/metainfo', ['linux/eric6.appdata.xml']),
         ]
-    elif os.name == 'nt':
+    elif sys.platform.startswith(("win", "cygwin")):
         dataFiles = [
             ('scripts', [
                 'eric6/pixmaps/eric6.ico',
@@ -81,26 +94,148 @@
 ## make Linux detect eric6 desktop files
 ######################################################################
 
-class Eric6InstallData(install_data):
+class Eric6InstallDataWrapper(install_data):
+    """
+    Class providing an install_data command wrapper to perform
+    post-installation tasks.
+    """
     def run(self):
+        """
+        Public method to perform the data installation.
+        """
+        # do the distutils install_data first
         install_data.run(self)
+        
+        # now perform our post installation stuff
         if sys.platform.startswith('linux'):
             try:
                 subprocess.call(['update-desktop-database'])
             except:
                 print("ERROR: unable to update desktop database",
                       file=sys.stderr)
+
 CmdClass = {
-    'install_data': Eric6InstallData,
+    'install_data': Eric6InstallDataWrapper,
 }
 
 ######################################################################
+## functions to prepare the sources for building
+######################################################################
+
+def prepareInfoFile(fileName, version):
+    """
+    Function to prepare an Info.py file when installing from source.
+    
+    @param fileName name of the Python file containing the info (string)
+    @param version version string for the package (string)
+    """
+    if not fileName:
+        return
+    
+    try:
+        os.rename(fileName, fileName + ".orig")
+    except EnvironmentError:
+        pass
+    try:
+        hgOut = subprocess.check_output(["hg", "identify", "-i"])
+        if sys.version_info[0] == 3:
+            hgOut = hgOut.decode()
+    except (OSError, subprocess.CalledProcessError):
+        hgOut = ""
+    if hgOut:
+        hgOut = hgOut.strip()
+        if hgOut.endswith("+"):
+            hgOut = hgOut[:-1]
+        f = open(fileName + ".orig", "r", encoding="utf-8")
+        text = f.read()
+        f.close()
+        text = text.replace("@@REVISION@@", hgOut)\
+            .replace("@@VERSION@@", version)
+        f = open(fileName, "w")
+        f.write(text)
+        f.close()
+    else:
+        shutil.copy(fileName + ".orig", fileName)
+
+
+def cleanupSource(dirName):
+    """
+    Cleanup the sources directory to get rid of leftover files
+    and directories.
+    
+    @param dirName name of the directory to prune (string)
+    """
+    # step 1: delete all Ui_*.py files without a corresponding
+    #         *.ui file
+    dirListing = os.listdir(dirName)
+    for formName, sourceName in [
+        (f.replace('Ui_', "").replace(".py", ".ui"), f)
+            for f in dirListing if fnmatch.fnmatch(f, "Ui_*.py")]:
+        if not os.path.exists(os.path.join(dirName, formName)):
+            os.remove(os.path.join(dirName, sourceName))
+            if os.path.exists(os.path.join(dirName, sourceName + "c")):
+                os.remove(os.path.join(dirName, sourceName + "c"))
+    
+    # step 2: delete the __pycache__ directory and all remaining *.pyc files
+    if os.path.exists(os.path.join(dirName, "__pycache__")):
+        shutil.rmtree(os.path.join(dirName, "__pycache__"))
+    for name in [f for f in os.listdir(dirName)
+                 if fnmatch.fnmatch(f, "*.pyc")]:
+        os.remove(os.path.join(dirName, name))
+    
+    # step 3: delete *.orig files
+    for name in [f for f in os.listdir(dirName)
+                 if fnmatch.fnmatch(f, "*.orig")]:
+        os.remove(os.path.join(dirName, name))
+    
+    # step 4: descent into subdirectories and delete them if empty
+    for name in os.listdir(dirName):
+        name = os.path.join(dirName, name)
+        if os.path.isdir(name):
+            cleanupSource(name)
+            if len(os.listdir(name)) == 0:
+                os.rmdir(name)
+
+
+def __pyName(py_dir, py_file):
+    """
+    Local function to create the Python source file name for the compiled
+    .ui file.
+    
+    @param py_dir suggested name of the directory (string)
+    @param py_file suggested name for the compile source file (string)
+    @return tuple of directory name (string) and source file name (string)
+    """
+    return py_dir, "Ui_{0}".format(py_file)
+
+
+def compileUiFiles(dirName):
+    """
+    Compile the .ui files to Python sources.
+    
+    @param dirName name of the directory to compile UI files for (string)
+    """
+    from PyQt5.uic import compileUiDir
+    compileUiDir(dirName, True, __pyName)
+
+######################################################################
 ## setup() below
 ######################################################################
 
+Version = getVersion()
+sourceDir = os.path.join(os.path.dirname(__file__), "eric6")
+infoFileName = os.path.join(sourceDir, "UI", "Info.py")
+if sys.argv[1].startswith("bdist"):
+    # prepare the sources under "eric6" for building the wheel file
+    print("preparing the sources...")
+    cleanupSource(sourceDir)
+    compileUiFiles(sourceDir)
+    prepareInfoFile(infoFileName, Version)
+    print("Preparation finished")
+
 setup(
     name="eric6",
-    version=getVersion(),
+    version=Version,
     description="eric6 is an integrated development environment for the"
         " Python language.",
     long_description="eric6 is an integrated development environment for the"
@@ -148,18 +283,18 @@
         "pip",
         "docutils",
         "Markdown",
+        "pywin32>=1.0;platform_system=='Windows'",
     ],
     data_files=getDataFiles(),
     packages=find_packages(),
-#    include_package_data=True,
     zip_safe=False,
     package_data={
         "": getPackageData(
             "eric6",
             [".png", ".svg", ".svgz", ".xpm", ".ico", ".gif", ".icns", ".txt",
              ".style", ".tmpl", ".html", ".qch", ".css", ".qss", ".e4h",
-             ".e6h", ".api", ".bas" ".dat"]) + 
-            ["i18n/eric6_de.qm", "i18n/eric6_en.qm", "i18n/eric6_es.qm",
+             ".e6h", ".api", ".bas" ".dat"]
+        ) + ["i18n/eric6_de.qm", "i18n/eric6_en.qm", "i18n/eric6_es.qm",
              "i18n/eric6_ru.qm",
         ]
     },
@@ -190,7 +325,15 @@
         "console_scripts":[
             "eric6_api = eric6.eric6_api:main",
             "eric6_doc = eric6.eric6_doc:main",
+            "eric6_post_install = eric6.eric6_post_install:main"
         ],
     },
     cmdclass=CmdClass,
 )
+
+if os.path.exists(infoFileName + ".orig"):
+    try:
+        os.remove(infoFileName)
+        os.rename(infoFileName + ".orig", infoFileName)
+    except EnvironmentError:
+        pass

eric ide

mercurial