Tue, 16 Apr 2019 19:43:53 +0200
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