Sun, 03 Dec 2023 19:46:34 +0100
Corrected some uses of dict.keys(), dict.values() and dict.items().
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (c) 2002 - 2023 Detlev Offenbach <detlev@die-offenbachs.de> # # This is the install script for eric. """ Installation script for the eric IDE and all eric related tools. """ import argparse import compileall import contextlib import datetime import fnmatch import getpass import glob import importlib import io import json import os import py_compile import re import shlex import shutil import subprocess # secok import sys import sysconfig import time from functools import partial # Define the globals. currDir = os.getcwd() modDir = None pyModDir = None platBinDir = None distDir = None apisDir = None installApis = True doCleanup = True doCleanDesktopLinks = False forceCleanDesktopLinks = False doCompile = True yes2All = False withPyqt6Tools = False verbose = False cfg = {} progLanguages = ["MicroPython", "Python3", "QSS"] sourceDir = "eric" eric7SourceDir = "" configName = "eric7config.py" defaultMacAppBundleName = "eric7.app" defaultMacAppBundlePath = "/Applications" defaultMacUserAppBundlePath = os.path.expanduser("~/Applications") defaultMacPythonExe = "{0}/Resources/Python.app/Contents/MacOS/Python".format( sys.exec_prefix ) if not os.path.exists(defaultMacPythonExe): defaultMacPythonExe = "" macAppBundleName = defaultMacAppBundleName macAppBundlePath = defaultMacAppBundlePath macPythonExe = defaultMacPythonExe createInstallInfoFile = True installInfoName = "eric7install.json" installInfo = {} installCwd = "" # Define blacklisted versions of the prerequisites BlackLists = { "sip": [], "PyQt6": [], "QScintilla2": [], } PlatformsBlackLists = { "freebsd": { "sip": [], "PyQt6": [], "QScintilla2": [], }, "linux": { "sip": [], "PyQt6": [], "QScintilla2": [], }, "mac": { "sip": [], "PyQt6": [], "QScintilla2": [], }, "windows": { "sip": [], "PyQt6": [], "QScintilla2": [], }, } def exit(rcode=0): """ Exit the install script. @param rcode result code to report back (integer) """ global currDir print() if sys.platform.startswith(("win", "cygwin")): with contextlib.suppress(EOFError): input("Press enter to continue...") # secok os.chdir(currDir) sys.exit(rcode) def initGlobals(): """ Module function to set the values of globals that need more than a simple assignment. """ global platBinDir, modDir, pyModDir, apisDir, macAppBundlePath if sys.platform.startswith(("win", "cygwin")): platBinDir = sysconfig.get_path("scripts") else: # install the eric scripts along the python executable platBinDir = sysconfig.get_path("scripts") if not os.access(platBinDir, os.W_OK): # install them in the user's bin directory platBinDir = os.path.expanduser("~/bin") modDir = sysconfig.get_path("platlib") if not os.access(modDir, os.W_OK): # can't write to the standard path, use the 'user' path instead if sys.platform.startswith(("win", "cygwin")): scheme = "nt_user" elif sys.platform == "darwin": scheme = "osx_framework_user" else: scheme = "posix_user" modDir = sysconfig.get_path("platlib", scheme) pyModDir = modDir pyqtDataDir = os.path.join(modDir, "PyQt6") if os.path.exists(os.path.join(pyqtDataDir, "qsci")): # it's the installer qtDataDir = pyqtDataDir elif os.path.exists(os.path.join(pyqtDataDir, "Qt6", "qsci")): # it's the wheel qtDataDir = os.path.join(pyqtDataDir, "Qt6") else: # determine dynamically try: from PyQt6.QtCore import QLibraryInfo # noqa: I101, I102 qtDataDir = QLibraryInfo.path(QLibraryInfo.LibraryPath.DataPath) except ImportError: qtDataDir = None apisDir = os.path.join(qtDataDir, "qsci", "api") if qtDataDir else None if sys.platform == "darwin": if os.getuid() == 0: macAppBundlePath = defaultMacAppBundlePath else: macAppBundlePath = defaultMacUserAppBundlePath def copyToFile(name, text): """ Copy a string to a file. @param name the name of the file. @param text the contents to copy to the file. """ with open(name, "w") as f: f.write(text) def copyDesktopFile(src, dst): """ Modify a desktop file and write it to its destination. @param src source file name (string) @param dst destination file name (string) """ global cfg, platBinDir with open(src, "r", encoding="utf-8") as f: text = f.read() text = text.replace("@BINDIR@", platBinDir) text = text.replace("@MARKER@", "") text = text.replace("@PY_MARKER@", "") dstPath = os.path.dirname(dst) if not os.path.isdir(dstPath): os.makedirs(dstPath) with open(dst, "w", encoding="utf-8") as f: f.write(text) os.chmod(dst, 0o644) def copyAppStreamFile(src, dst): """ Modify an appstream file and write it to its destination. @param src source file name (string) @param dst destination file name (string) """ if os.path.exists(os.path.join("eric", "src", "eric7", "UI", "Info.py")): # Installing from installer archive from eric.src.eric7.UI.Info import Version # noqa: I101, I102 elif os.path.exists(os.path.join("src", "eric7", "UI", "Info.py")): # Installing from source tree from src.eric7.UI.Info import Version # noqa: I101, I102 else: Version = "Unknown" with open(src, "r", encoding="utf-8") as f: text = f.read() text = ( text.replace("@MARKER@", "") .replace("@VERSION@", Version.split(None, 1)[0]) .replace("@DATE@", time.strftime("%Y-%m-%d")) ) dstPath = os.path.dirname(dst) if not os.path.isdir(dstPath): os.makedirs(dstPath) with open(dst, "w", encoding="utf-8") as f: f.write(text) os.chmod(dst, 0o644) def wrapperNames(dname, wfile): """ Create the platform specific names for the wrapper script. @param dname name of the directory to place the wrapper into @param wfile basename (without extension) of the wrapper script @return the names of the wrapper scripts """ wnames = ( (dname + "\\" + wfile + ".cmd", dname + "\\" + wfile + ".bat") if sys.platform.startswith(("win", "cygwin")) else (dname + "/" + wfile,) ) return wnames def createPyWrapper(pydir, wfile, saveDir, isGuiScript=True): """ Create an executable wrapper for a Python script. @param pydir the name of the directory where the Python script will eventually be installed (string) @param wfile the basename of the wrapper (string) @param saveDir directory to save the file into (string) @param isGuiScript flag indicating a wrapper script for a GUI application (boolean) @return the platform specific name of the wrapper (string) """ # all kinds of Windows systems if sys.platform.startswith(("win", "cygwin")): wname = wfile + ".cmd" if isGuiScript: wrapper = ( """@echo off\n""" '''start "" "{2}\\pythonw.exe"''' ''' "{0}\\{1}.pyw"''' """ %1 %2 %3 %4 %5 %6 %7 %8 %9\n""".format( pydir, wfile, os.path.dirname(sys.executable) ) ) else: wrapper = ( '''@"{0}" "{1}\\{2}.py"''' """ %1 %2 %3 %4 %5 %6 %7 %8 %9\n""".format(sys.executable, pydir, wfile) ) # Mac OS X elif sys.platform == "darwin": major = sys.version_info.major pyexec = "{0}/bin/pythonw{1}".format(sys.exec_prefix, major) if not os.path.exists(pyexec): pyexec = "{0}/bin/python{1}".format(sys.exec_prefix, major) wname = wfile wrapper = ( """#!/bin/sh\n""" """\n""" """exec "{0}" "{1}/{2}.py" "$@"\n""".format(pyexec, pydir, wfile) ) # *nix systems else: wname = wfile wrapper = ( """#!/bin/sh\n""" """\n""" """exec "{0}" "{1}/{2}.py" "$@"\n""".format(sys.executable, pydir, wfile) ) wname = os.path.join(saveDir, wname) copyToFile(wname, wrapper) os.chmod(wname, 0o755) # secok return wname def copyTree(src, dst, filters, excludeDirs=None, excludePatterns=None): """ Copy Python, translation, documentation, wizards configuration, designer template files and DTDs of a directory tree. @param src name of the source directory @param dst name of the destination directory @param filters list of filter pattern determining the files to be copied @param excludeDirs list of (sub)directories to exclude from copying @param excludePatterns list of filter pattern determining the files to be skipped """ if excludeDirs is None: excludeDirs = [] if excludePatterns is None: excludePatterns = [] try: names = os.listdir(src) except OSError: # ignore missing directories (most probably the i18n directory) return for name in names: skipIt = False for excludePattern in excludePatterns: if fnmatch.fnmatch(name, excludePattern): skipIt = True break if not skipIt: srcname = os.path.join(src, name) dstname = os.path.join(dst, name) for fileFilter in filters: if fnmatch.fnmatch(srcname, fileFilter): if not os.path.isdir(dst): os.makedirs(dst) shutil.copy2(srcname, dstname) os.chmod(dstname, 0o644) break else: if os.path.isdir(srcname) and srcname not in excludeDirs: copyTree(srcname, dstname, filters, excludePatterns=excludePatterns) def createGlobalPluginsDir(): """ Create the global plugins directory, if it doesn't exist. """ global cfg, distDir pdir = os.path.join(cfg["mdir"], "eric7plugins") fname = os.path.join(pdir, "__init__.py") if not os.path.exists(fname): if not os.path.exists(pdir): os.mkdir(pdir, 0o755) with open(fname, "w") as f: f.write( '''# -*- coding: utf-8 -*- """ Package containing the global plugins. """ ''' ) os.chmod(fname, 0o644) 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 cleanUp(): """ Uninstall the old eric files. """ global platBinDir try: from eric7.Globals import getConfig # noqa: I101 except ImportError: # eric wasn't installed previously return except SyntaxError: # an incomplete or old config file was found return global pyModDir, progLanguages # Remove the menu entry for Linux systems if sys.platform.startswith(("linux", "freebsd")): cleanUpLinuxSpecifics() # Remove the Desktop and Start Menu entries for Windows systems elif sys.platform.startswith(("win", "cygwin")): cleanUpWindowsLinks() # Remove the wrapper scripts rem_wnames = [ "eric7_api", "eric7_browser", "eric7_compare", "eric7_configure", "eric7_diff", "eric7_doc", "eric7_editor", "eric7_hexeditor", "eric7_iconeditor", "eric7_ide", "eric7_pdf", "eric7_pip", "eric7_plugininstall", "eric7_pluginrepository", "eric7_pluginuninstall", "eric7_qregularexpression", "eric7_re", "eric7_shell", "eric7_snap", "eric7_sqlbrowser", "eric7_testing", "eric7_tray", "eric7_trpreviewer", "eric7_uipreviewer", "eric7_virtualenv", # obsolete scripts below "eric7_unittest", "eric7", ] try: dirs = [platBinDir, getConfig("bindir")] for rem_wname in rem_wnames: for d in dirs: for rwname in wrapperNames(d, rem_wname): if os.path.exists(rwname): os.remove(rwname) # Cleanup our config file(s) for name in ["eric7config.py", "eric7config.pyc", "eric7.pth"]: e6cfile = os.path.join(pyModDir, name) if os.path.exists(e6cfile): os.remove(e6cfile) e6cfile = os.path.join(pyModDir, "__pycache__", name) path, ext = os.path.splitext(e6cfile) for f in glob.glob("{0}.*{1}".format(path, ext)): os.remove(f) # Cleanup the install directories for name in [ "ericExamplesDir", "ericDocDir", "ericDTDDir", "ericCSSDir", "ericIconDir", "ericPixDir", "ericTemplatesDir", "ericCodeTemplatesDir", "ericOthersDir", "ericStylesDir", "ericThemesDir", "ericDir", ]: with contextlib.suppress(AttributeError): if os.path.exists(getConfig(name)): shutil.rmtree(getConfig(name), True) # Cleanup translations for name in glob.glob( os.path.join(getConfig("ericTranslationsDir"), "eric7_*.qm") ): if os.path.exists(name): os.remove(name) # Cleanup API files with contextlib.suppress(AttributeError): apidir = getConfig("apidir") for progLanguage in progLanguages: for name in getConfig("apis"): # step 1: programming language as given apiname = os.path.join(apidir, progLanguage, name) if os.path.exists(apiname): os.remove(apiname) # step 2: programming language as lowercase apiname = os.path.join(apidir, progLanguage.lower(), name) if os.path.exists(apiname): os.remove(apiname) for apiname in glob.glob( os.path.join(apidir, progLanguage, "*.bas") ) + glob.glob(os.path.join(apidir, progLanguage.lower(), "*.bas")): if os.path.exists(apiname): os.remove(apiname) # remove empty directories with contextlib.suppress(FileNotFoundError, OSError): os.rmdir(os.path.join(apidir, progLanguage)) with contextlib.suppress(FileNotFoundError, OSError): os.rmdir(os.path.join(apidir, progLanguage.lower())) if sys.platform == "darwin": # delete the Mac app bundle cleanUpMacAppBundle() except OSError as msg: sys.stderr.write("Error: {0}\nTry install with admin rights.\n".format(msg)) exit(7) def cleanUpLinuxSpecifics(): """ Clean up Linux specific files. """ if os.getuid() == 0: for name in [ "/usr/share/appdata/eric7.appdata.xml", "/usr/share/metainfo/eric7.appdata.xml", "/usr/share/applications/eric7_browser.desktop", "/usr/share/applications/eric7_ide.desktop", "/usr/share/icons/eric.png", "/usr/share/icons/ericWeb.png", "/usr/share/pixmaps/eric.png", "/usr/share/pixmaps/ericWeb.png", # obsolete entries below "/usr/share/applications/eric7.desktop", ]: if os.path.exists(name): os.remove(name) elif os.getuid() >= 1000: # it is assumed that user ids start at 1000 for name in [ "~/.local/share/appdata/eric7.appdata.xml", "~/.local/share/metainfo/eric7.appdata.xml", "~/.local/share/applications/eric7_browser.desktop", "~/.local/share/applications/eric7_ide.desktop", "~/.local/share/icons/eric.png", "~/.local/share/icons/ericWeb.png", "~/.local/share/pixmaps/eric.png", "~/.local/share/pixmaps/ericWeb.png", # obsolete entries below "~/.local/share/applications/eric7.desktop", ]: path = os.path.expanduser(name) if os.path.exists(path): os.remove(path) def cleanUpMacAppBundle(): """ Uninstall the macOS application bundle. """ from eric7.Globals import getConfig try: macAppBundlePath = getConfig("macAppBundlePath") macAppBundleName = getConfig("macAppBundleName") except AttributeError: macAppBundlePath = ( defaultMacAppBundlePath if os.getpid() == 0 else defaultMacUserAppBundlePath ) macAppBundleName = defaultMacAppBundleName for bundlePath in [ os.path.join(defaultMacAppBundlePath, macAppBundleName), os.path.join(defaultMacUserAppBundlePath, macAppBundleName), os.path.join(macAppBundlePath, macAppBundleName), ]: if os.path.exists(bundlePath): shutil.rmtree(bundlePath) def cleanUpWindowsLinks(): """ Clean up the Desktop and Start Menu entries for Windows. """ if importlib.util.find_spec("pywintypes") is None: # links were not created by install.py return global doCleanDesktopLinks, forceCleanDesktopLinks regPath = ( "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders" ) if doCleanDesktopLinks or forceCleanDesktopLinks: # 1. cleanup desktop links regName = "Desktop" desktopEntry = getWinregEntry(regName, regPath) if desktopEntry: desktopFolder = os.path.normpath(os.path.expandvars(desktopEntry)) for linkName in windowsDesktopNames(clean=True): linkPath = os.path.join(desktopFolder, linkName) if os.path.exists(linkPath): try: os.remove(linkPath) except OSError: # maybe restrictions prohibited link removal print("Could not remove '{0}'.".format(linkPath)) # 2. cleanup start menu entry regName = "Programs" programsEntry = getWinregEntry(regName, regPath) if programsEntry: programsFolder = os.path.normpath(os.path.expandvars(programsEntry)) eric7EntryPath = os.path.join(programsFolder, windowsProgramsEntry()) if os.path.exists(eric7EntryPath): try: shutil.rmtree(eric7EntryPath) except OSError: # maybe restrictions prohibited link removal print("Could not remove '{0}'.".format(eric7EntryPath)) def shutilCopy(src, dst, perm=0o644): """ Wrapper function around shutil.copy() to ensure the permissions. @param src source file name (string) @param dst destination file name or directory name (string) @param perm permissions to be set (integer) """ shutil.copy(src, dst) if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) os.chmod(dst, perm) def installEric(): """ Actually perform the installation steps. @return result code (integer) """ global distDir, doCleanup, cfg, progLanguages, sourceDir, configName global installApis # Create the platform specific wrappers. scriptsDir = "install_scripts" if not os.path.isdir(scriptsDir): os.mkdir(scriptsDir) wnames = [] for name in ["eric7_api", "eric7_doc"]: wnames.append(createPyWrapper(cfg["ericDir"], name, scriptsDir, False)) for name in [ "eric7_browser", "eric7_compare", "eric7_configure", "eric7_diff", "eric7_editor", "eric7_hexeditor", "eric7_iconeditor", "eric7_ide", "eric7_pdf", "eric7_pip", "eric7_plugininstall", "eric7_pluginrepository", "eric7_pluginuninstall", "eric7_qregularexpression", "eric7_re", "eric7_shell", "eric7_snap", "eric7_sqlbrowser", "eric7_tray", "eric7_trpreviewer", "eric7_uipreviewer", "eric7_testing", "eric7_virtualenv", ]: wnames.append(createPyWrapper(cfg["ericDir"], name, scriptsDir)) # set install prefix, if not None if distDir: for key in cfg: cfg[key] = os.path.normpath(os.path.join(distDir, cfg[key].lstrip(os.sep))) try: # Install the files # make the install directories for key in cfg: if cfg[key] and not os.path.isdir(cfg[key]): os.makedirs(cfg[key]) # copy the eric config file if distDir: shutilCopy(configName, cfg["mdir"]) if os.path.exists(configName + "c"): shutilCopy(configName + "c", cfg["mdir"]) else: shutilCopy(configName, modDir) if os.path.exists(configName + "c"): shutilCopy(configName + "c", modDir) # copy the various parts of eric copyTree( eric7SourceDir, cfg["ericDir"], ["*.py", "*.pyc", "*.pyo", "*.pyw"], excludePatterns=["eric7config.py*"], ) copyTree( os.path.join(eric7SourceDir, "Plugins"), os.path.join(cfg["ericDir"], "Plugins"), ["*.svgz", "*.svg", "*.png", "*.style", "*.tmpl", "*.txt"], ) copyTree( os.path.join(eric7SourceDir, "Documentation"), cfg["ericDocDir"], ["*.html", "*.qch"], ) copyTree(os.path.join(eric7SourceDir, "CSSs"), cfg["ericCSSDir"], ["*.css"]) copyTree( os.path.join(eric7SourceDir, "Styles"), cfg["ericStylesDir"], ["*.qss", "*.ehj"], ) copyTree( os.path.join(eric7SourceDir, "Themes"), cfg["ericThemesDir"], ["*.ethj"] ) copyTree( os.path.join(eric7SourceDir, "i18n"), cfg["ericTranslationsDir"], ["*.qm"] ) copyTree( os.path.join(eric7SourceDir, "icons"), cfg["ericIconDir"], ["*.svgz", "*.svg", "*.png", "LICENSE*.*", "readme.txt"], ) copyTree( os.path.join(eric7SourceDir, "pixmaps"), cfg["ericPixDir"], ["*.svgz", "*.svg", "*.png", "*.xpm", "*.ico", "*.gif"], ) copyTree( os.path.join(eric7SourceDir, "DesignerTemplates"), cfg["ericTemplatesDir"], ["*.tmpl"], ) copyTree( os.path.join(eric7SourceDir, "CodeTemplates"), cfg["ericCodeTemplatesDir"], ["*.tmpl"], ) copyTree( os.path.join(eric7SourceDir, "DebugClients", "Python", "coverage"), os.path.join(cfg["ericDir"], "DebugClients", "Python", "coverage"), ["*.js", "*.html", "*.png", "*.css", "*.scss", "*.txt", "*.rst"], ) # copy some data files needed at various places copyTree( os.path.join(eric7SourceDir, "data"), os.path.join(cfg["ericDir"], "data"), ["*.txt"], ) copyTree( os.path.join(eric7SourceDir, "EricNetwork", "data"), os.path.join(cfg["ericDir"], "EricNetwork", "data"), ["*.dat", "*.txt"], ) copyTree( os.path.join(eric7SourceDir, "IconEditor", "cursors"), os.path.join(cfg["ericDir"], "IconEditor", "cursors"), ["*.xpm"], ) copyTree( os.path.join(eric7SourceDir, "MicroPython", "Tools"), os.path.join(cfg["ericDir"], "MicroPython", "Tools"), ["*.json"], ) copyTree( os.path.join(eric7SourceDir, "MicroPython", "BluetoothDialogs", "data"), os.path.join(cfg["ericDir"], "MicroPython", "BluetoothDialogs", "data"), ["*.yaml"], ) copyTree( os.path.join(eric7SourceDir, "UI", "data"), os.path.join(cfg["ericDir"], "UI", "data"), ["*.css"], ) copyTree( os.path.join(eric7SourceDir, "WebBrowser"), os.path.join(cfg["ericDir"], "WebBrowser"), ["*.xbel", "*.xml", "*.html", "*.png", "*.gif", "*.js"], ) # copy the wrappers for wname in wnames: shutilCopy(wname, cfg["bindir"], perm=0o755) os.remove(wname) shutil.rmtree(scriptsDir) # copy the license file shutilCopy(os.path.join(sourceDir, "docs", "LICENSE.txt"), cfg["ericDir"]) # create the global plugins directory createGlobalPluginsDir() except OSError as msg: sys.stderr.write("Error: {0}\nTry install with admin rights.\n".format(msg)) return 7 # copy some text files to the doc area for name in ["LICENSE.txt", "THANKS", "changelog.md", "ThirdParty.md"]: try: shutilCopy(os.path.join(sourceDir, "docs", name), cfg["ericDocDir"]) except OSError: print( "\nCould not install '{0}'.".format( os.path.join(sourceDir, "docs", name) ) ) for name in glob.glob(os.path.join(sourceDir, "docs", "README*.*")): try: shutilCopy(name, cfg["ericDocDir"]) except OSError: print("\nCould not install '{0}'.".format(name)) # copy some more stuff for name in ("default.ekj", "default_Mac.ekj", "default.e4k", "default_Mac.e4k"): try: shutilCopy(os.path.join(sourceDir, "others", name), cfg["ericOthersDir"]) except OSError: print( "\nCould not install '{0}'.".format( os.path.join(sourceDir, "others", name) ) ) # install the API file if installApis: if os.access(cfg["apidir"], os.W_OK): for progLanguage in progLanguages: apidir = os.path.join(cfg["apidir"], progLanguage) if not os.path.exists(apidir): os.makedirs(apidir) for apiName in glob.glob( os.path.join(eric7SourceDir, "APIs", progLanguage, "*.api") ): shutilCopy(apiName, apidir) for apiName in glob.glob( os.path.join(eric7SourceDir, "APIs", progLanguage, "*.bas") ): shutilCopy(apiName, apidir) else: print("\nThe API directory '{0}' is not writable.".format(cfg["apidir"])) print("Use the API files provided by the 'API Files' plug-in.") # Create menu entry for Linux systems if sys.platform.startswith(("linux", "freebsd")): createLinuxSpecifics() # Create Desktop and Start Menu entries for Windows systems elif sys.platform.startswith(("win", "cygwin")): createWindowsLinks() # Create a Mac application bundle elif sys.platform == "darwin": createMacAppBundle() return 0 def createLinuxSpecifics(): """ Install Linux specific files. """ global distDir, sourceDir dataSourceDir = os.path.join(eric7SourceDir, "data", "linux") if distDir: dst = os.path.normpath(os.path.join(distDir, "usr/share/icons")) if not os.path.exists(dst): os.makedirs(dst) shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "eric_icon.png"), os.path.join(dst, "eric.png"), ) shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"), os.path.join(dst, "ericWeb.png"), ) dst = os.path.normpath( os.path.join(distDir, "usr/share/icons/hicolor/48x48/apps") ) if not os.path.exists(dst): os.makedirs(dst) shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "eric48_icon.png"), os.path.join(dst, "eric.png"), ) shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"), os.path.join(dst, "ericWeb.png"), ) dst = os.path.normpath(os.path.join(distDir, "usr/share/applications")) if not os.path.exists(dst): os.makedirs(dst) copyDesktopFile( os.path.join(dataSourceDir, "eric7_ide.desktop.in"), os.path.join(dst, "eric7_ide.desktop"), ) copyDesktopFile( os.path.join(dataSourceDir, "eric7_browser.desktop.in"), os.path.join(dst, "eric7_browser.desktop"), ) dst = os.path.normpath(os.path.join(distDir, "usr/share/metainfo")) if not os.path.exists(dst): os.makedirs(dst) copyAppStreamFile( os.path.join(dataSourceDir, "eric7.appdata.xml.in"), os.path.join(dst, "eric7.appdata.xml"), ) elif os.getuid() == 0: shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "eric_icon.png"), "/usr/share/icons/eric.png", ) shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "eric48_icon.png"), "/usr/share/icons/hicolor/48x48/apps/eric.png", ) copyDesktopFile( os.path.join(dataSourceDir, "eric7_ide.desktop.in"), "/usr/share/applications/eric7_ide.desktop", ) if os.path.exists("/usr/share/metainfo"): copyAppStreamFile( os.path.join(dataSourceDir, "eric7.appdata.xml.in"), "/usr/share/metainfo/eric7.appdata.xml", ) elif os.path.exists("/usr/share/appdata"): copyAppStreamFile( os.path.join(dataSourceDir, "eric7.appdata.xml.in"), "/usr/share/appdata/eric7.appdata.xml", ) shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"), "/usr/share/icons/ericWeb.png", ) shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"), "/usr/share/icons/hicolor/48x48/apps/ericWeb.png", ) copyDesktopFile( os.path.join(dataSourceDir, "eric7_browser.desktop.in"), "/usr/share/applications/eric7_browser.desktop", ) elif os.getuid() >= 1000: # it is assumed, that user ids start at 1000 localPath = os.path.join(os.path.expanduser("~"), ".local", "share") # create directories first for directory in [ os.path.join(localPath, name) for name in ( "icons", "icons/hicolor/48x48/apps", "applications", "metainfo", "appdata", ) ]: if not os.path.isdir(directory): os.makedirs(directory) # now copy the files shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "eric_icon.png"), os.path.join(localPath, "icons", "eric.png"), ) shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "eric48_icon.png"), os.path.join(localPath, "icons/hicolor/48x48/apps", "eric.png"), ) copyDesktopFile( os.path.join(dataSourceDir, "eric7_ide.desktop.in"), os.path.join(localPath, "applications", "eric7_ide.desktop"), ) copyAppStreamFile( os.path.join(dataSourceDir, "eric7.appdata.xml.in"), os.path.join(localPath, "metainfo", "eric7.appdata.xml"), ) copyAppStreamFile( os.path.join(dataSourceDir, "eric7.appdata.xml.in"), os.path.join(localPath, "appdata", "eric7.appdata.xml"), ) shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"), os.path.join(localPath, "icons", "ericWeb.png"), ) shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"), os.path.join(localPath, "icons/hicolor/48x48/apps", "ericWeb.png"), ) copyDesktopFile( os.path.join(dataSourceDir, "eric7_browser.desktop.in"), os.path.join(localPath, "applications", "eric7_browser.desktop"), ) def createWindowsLinks(): """ Create Desktop and Start Menu links. """ if importlib.util.find_spec("win32com") is None: installed = pipInstall( "pywin32", "\nThe Python package 'pywin32' could not be imported.", force=False, ) if installed: # create the links via an external script to get around some # startup magic done by pywin32.pth args = [ sys.executable, os.path.join(os.path.dirname(__file__), "create_windows_links.py"), ] subprocess.run(args) # secok else: print( "\nThe Python package 'pywin32' is not installed. Desktop and" " Start Menu entries will not be created." ) return regPath = ( "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders" ) # 1. create desktop shortcuts regName = "Desktop" desktopEntry = getWinregEntry(regName, regPath) if desktopEntry: desktopFolder = os.path.normpath(os.path.expandvars(desktopEntry)) 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)) eric7EntryPath = os.path.join(programsFolder, windowsProgramsEntry()) if not os.path.exists(eric7EntryPath): try: os.makedirs(eric7EntryPath) except OSError: # maybe restrictions prohibited link creation return for linkName, targetPath, iconPath in windowsDesktopEntries(): linkPath = os.path.join(eric7EntryPath, linkName) createWindowsShortcut(linkPath, targetPath, iconPath) def createMacAppBundle(): """ Create a Mac application bundle. """ global cfg, macAppBundleName, macPythonExe, macAppBundlePath directories = { "contents": "{0}/{1}/Contents/".format(macAppBundlePath, macAppBundleName), "exe": "{0}/{1}/Contents/MacOS".format(macAppBundlePath, macAppBundleName), "icns": "{0}/{1}/Contents/Resources".format(macAppBundlePath, macAppBundleName), } for directory in directories.values(): if not os.path.exists(directory): os.makedirs(directory) if macPythonExe == defaultMacPythonExe and macPythonExe: starter = os.path.join(directories["exe"], "eric") os.symlink(macPythonExe, starter) else: starter = "python{0}".format(sys.version_info.major) wname = os.path.join(directories["exe"], "eric7_ide") # determine entry for DYLD_FRAMEWORK_PATH dyldLine = "" try: from PyQt6.QtCore import QLibraryInfo # noqa: I101, I102 qtLibraryDir = QLibraryInfo.path(QLibraryInfo.LibraryPath.LibrariesPath) except ImportError: qtLibraryDir = "" if qtLibraryDir: dyldLine = "DYLD_FRAMEWORK_PATH={0}\n".format(qtLibraryDir) # determine entry for PATH pathLine = "" path = os.getenv("PATH", "") if path: pybin = os.path.join(sys.exec_prefix, "bin") pathlist = path.split(os.pathsep) pathlist_n = [pybin] for path_ in pathlist: if path_ and path_ not in pathlist_n: pathlist_n.append(path_) pathLine = "PATH={0}\n".format(os.pathsep.join(pathlist_n)) # create the wrapper script wrapper = ( """#!/bin/sh\n""" """\n""" """{0}""" """{1}""" """exec "{2}" "-m" "eric7" "$@"\n""".format(pathLine, dyldLine, starter) ) copyToFile(wname, wrapper) os.chmod(wname, 0o755) # secok shutilCopy( os.path.join(eric7SourceDir, "pixmaps", "eric_2.icns"), os.path.join(directories["icns"], "eric.icns"), ) if os.path.exists(os.path.join("eric", "eric7", "UI", "Info.py")): # Installing from archive from eric.eric7.UI.Info import CopyrightShort, Version # noqa: I101, I102 elif os.path.exists(os.path.join("eric7", "UI", "Info.py")): # Installing from source tree from eric7.UI.Info import CopyrightShort, Version # noqa: I101 else: Version = "Unknown" CopyrightShort = "(c) 2002 - 2023 Detlev Offenbach" copyToFile( os.path.join(directories["contents"], "Info.plist"), """<?xml version="1.0" encoding="UTF-8"?>\n""" """<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"\n""" """ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n""" """<plist version="1.0">\n""" """<dict>\n""" """ <key>CFBundleExecutable</key>\n""" """ <string>eric7_ide</string>\n""" """ <key>CFBundleIconFile</key>\n""" """ <string>eric.icns</string>\n""" """ <key>CFBundleInfoDictionaryVersion</key>\n""" """ <string>{1}</string>\n""" """ <key>CFBundleName</key>\n""" """ <string>{0}</string>\n""" """ <key>CFBundleDisplayName</key>\n""" """ <string>{0}</string>\n""" """ <key>CFBundlePackageType</key>\n""" """ <string>APPL</string>\n""" """ <key>CFBundleSignature</key>\n""" """ <string>ERIC-IDE</string>\n""" """ <key>CFBundleVersion</key>\n""" """ <string>{1}</string>\n""" """ <key>CFBundleGetInfoString</key>\n""" """ <string>{1}, {2}</string>\n""" """ <key>CFBundleIdentifier</key>\n""" """ <string>org.python-projects.eric-ide</string>\n""" """ <key>NSRequiresAquaSystemAppearance</key>\n""" """ <string>false</string>\n""" """ <key>LSEnvironment</key>\n""" """ <dict>\n""" """ <key>LANG</key>\n""" """ <string>en_US.UTF-8</string>\n""" """ <key>LC_ALL</key>\n""" """ <string>en_US.UTF-8</string>\n""" """ <key>LC_CTYPE</key>\n""" """ <string>en_US.UTF-8</string>\n""" """ </dict>\n""" """</dict>\n""" """</plist>\n""".format( macAppBundleName.replace(".app", ""), Version.split(None, 1)[0], CopyrightShort, ), ) def createInstallConfig(): """ Create the installation config dictionary. """ global modDir, platBinDir, cfg, apisDir, installApis ericdir = os.path.join(modDir, "eric7") cfg = { "ericDir": ericdir, "ericPixDir": os.path.join(ericdir, "pixmaps"), "ericIconDir": os.path.join(ericdir, "icons"), "ericDTDDir": os.path.join(ericdir, "DTDs"), "ericCSSDir": os.path.join(ericdir, "CSSs"), "ericStylesDir": os.path.join(ericdir, "Styles"), "ericThemesDir": os.path.join(ericdir, "Themes"), "ericDocDir": os.path.join(ericdir, "Documentation"), "ericExamplesDir": os.path.join(ericdir, "Examples"), "ericTranslationsDir": os.path.join(ericdir, "i18n"), "ericTemplatesDir": os.path.join(ericdir, "DesignerTemplates"), "ericCodeTemplatesDir": os.path.join(ericdir, "CodeTemplates"), "ericOthersDir": ericdir, "bindir": platBinDir, "mdir": modDir, } if installApis: if apisDir: cfg["apidir"] = apisDir else: cfg["apidir"] = os.path.join(ericdir, "api") else: cfg["apidir"] = "" configLength = 16 def createConfig(): """ Create a config file with the respective config entries. """ global cfg, macAppBundlePath, configName apis = [] if installApis: for progLanguage in progLanguages: for apiName in sorted( glob.glob(os.path.join(eric7SourceDir, "APIs", progLanguage, "*.api")) ): apis.append(os.path.basename(apiName)) macConfig = ( ( """ 'macAppBundlePath': r'{0}',\n""" """ 'macAppBundleName': r'{1}',\n""" ).format(macAppBundlePath, macAppBundleName) if sys.platform == "darwin" else "" ) config = ( """# -*- coding: utf-8 -*-\n""" """#\n""" """# This module contains the configuration of the individual eric""" """ installation\n""" """#\n""" """\n""" """_pkg_config = {{\n""" """ 'ericDir': r'{0}',\n""" """ 'ericPixDir': r'{1}',\n""" """ 'ericIconDir': r'{2}',\n""" """ 'ericDTDDir': r'{3}',\n""" """ 'ericCSSDir': r'{4}',\n""" """ 'ericStylesDir': r'{5}',\n""" """ 'ericThemesDir': r'{6}',\n""" """ 'ericDocDir': r'{7}',\n""" """ 'ericExamplesDir': r'{8}',\n""" """ 'ericTranslationsDir': r'{9}',\n""" """ 'ericTemplatesDir': r'{10}',\n""" """ 'ericCodeTemplatesDir': r'{11}',\n""" """ 'ericOthersDir': r'{12}',\n""" """ 'bindir': r'{13}',\n""" """ 'mdir': r'{14}',\n""" """ 'apidir': r'{15}',\n""" """ 'apis': {16},\n""" """{17}""" """}}\n""" """\n""" """def getConfig(name):\n""" """ '''\n""" """ Module function to get a configuration value.\n""" """\n""" """ @param name name of the configuration value""" """ @type str\n""" """ @exception AttributeError raised to indicate an invalid""" """ config entry\n""" """ '''\n""" """ try:\n""" """ return _pkg_config[name]\n""" """ except KeyError:\n""" """ pass\n""" """\n""" """ raise AttributeError(\n""" """ '"{{0}}" is not a valid configuration value'""" """.format(name))\n""" ).format( cfg["ericDir"], cfg["ericPixDir"], cfg["ericIconDir"], cfg["ericDTDDir"], cfg["ericCSSDir"], cfg["ericStylesDir"], cfg["ericThemesDir"], cfg["ericDocDir"], cfg["ericExamplesDir"], cfg["ericTranslationsDir"], cfg["ericTemplatesDir"], cfg["ericCodeTemplatesDir"], cfg["ericOthersDir"], cfg["bindir"], cfg["mdir"], cfg["apidir"], sorted(apis), macConfig, ) copyToFile(configName, config) def createInstallInfo(): """ Record information about the way eric was installed. """ global createInstallInfoFile, installInfo, installCwd, cfg if createInstallInfoFile: installDateTime = datetime.datetime.now(tz=datetime.timezone.utc) try: installInfo["sudo"] = os.getuid() == 0 except AttributeError: installInfo["sudo"] = False installInfo["user"] = getpass.getuser() installInfo["exe"] = sys.executable installInfo["argv"] = " ".join(shlex.quote(a) for a in sys.argv[:]) installInfo["install_cwd"] = installCwd installInfo["eric"] = cfg["ericDir"] installInfo["virtualenv"] = installInfo["eric"].startswith( os.path.expanduser("~") ) installInfo["installed"] = True installInfo["installed_on"] = installDateTime.strftime("%Y-%m-%d %H:%M:%S") installInfo["guessed"] = False installInfo["edited"] = False installInfo["pip"] = False installInfo["remarks"] = "" installInfo["install_cwd_edited"] = False installInfo["exe_edited"] = False installInfo["argv_edited"] = False installInfo["eric_edited"] = False def pipInstall(packageName, message, force=True): """ Install the given package via pip. @param packageName name of the package to be installed @type str @param message message to be shown to the user @type str @param force flag indicating to perform the installation without asking the user @type bool @return flag indicating a successful installation @rtype bool """ global yes2All ok = False if yes2All or force: answer = "y" else: print( "{0}\nShall '{1}' be installed using pip? (Y/n)".format( message, packageName ), end=" ", ) answer = input() # secok if answer in ("", "Y", "y"): exitCode = subprocess.run( # secok [ sys.executable, "-m", "pip", "install", "--prefer-binary", "--upgrade", packageName, ] ).returncode ok = exitCode == 0 return ok def isPipOutdated(): """ Check, if pip is outdated. @return flag indicating an outdated pip @rtype bool """ try: pipOut = ( subprocess.run( # secok [sys.executable, "-m", "pip", "list", "--outdated", "--format=json"], check=True, capture_output=True, text=True, ) .stdout.strip() .splitlines()[0] ) # only the first line contains the JSON data except (OSError, subprocess.CalledProcessError): pipOut = "[]" # default empty list try: jsonList = json.loads(pipOut) except Exception: jsonList = [] for package in jsonList: if isinstance(package, dict) and package["name"] == "pip": print( "'pip' is outdated (installed {0}, available {1})".format( package["version"], package["latest_version"] ) ) return True return False def updatePip(): """ Update the installed pip package. """ global yes2All if yes2All: answer = "y" else: print("Shall 'pip' be updated (recommended)? (Y/n)", end=" ") answer = input() # secok if answer in ("", "Y", "y"): subprocess.run( # secok [sys.executable, "-m", "pip", "install", "--upgrade", "pip"] ) def versionToStr(version): """ Function to convert a version number into a version string. @param version version number to convert @type int @return version string @rtype str """ parts = [] while version: parts.append(version & 0xFF) version >>= 8 return ".".join(str(p) for p in reversed(parts)) def doDependancyChecks(): """ Perform some dependency checks. """ global verbose requiredVersions = { "pyqt6": 0x60200, # v6.2.0 "pyqt6-charts": 0x60200, # v6.2.0 "pyqt6-webengine": 0x60200, # v6.2.0 "pyqt6-qscintilla": 0x20D00, # v2.13.0 "sip": 0x60100, # v6.1.0 } try: isSudo = os.getuid() == 0 # disregard sudo installs on macOS except AttributeError: isSudo = False print("Checking dependencies") # update pip first even if we don't need to install anything if not isSudo and isPipOutdated(): updatePip() print("\n") # perform dependency checks if sys.version_info < (3, 8, 0) or sys.version_info >= (3, 13, 0): print("Sorry, you must have Python 3.8.0 or higher, but less 3.13.0.") print("Yours is {0}.".format(".".join(str(v) for v in sys.version_info[:3]))) exit(5) if ( importlib.util.find_spec("xml") is None or importlib.util.find_spec("xml.etree") is None ): print("Your Python installation is missing the XML module.") print("Please install it and try again.") exit(5) print("PyQt & Qt") print("---------") try: from PyQt6.QtCore import qVersion # noqa: I101, I102 except ImportError as err: msg = "'PyQt6' could not be detected.{0}".format( "\nError: {0}".format(err) if verbose else "" ) installed = not isSudo and pipInstall( "PyQt6>={0}".format(versionToStr(requiredVersions["pyqt6"])), msg ) if installed: # try to import it again try: from PyQt6.QtCore import qVersion # noqa: I101, I102 except ImportError as msg: print("Sorry, please install PyQt6.") print("Error: {0}".format(msg)) exit(1) else: print("Sorry, please install PyQt6.") print("Error: {0}".format(msg)) exit(1) print("Found PyQt6") pyuic = "pyuic6" if importlib.util.find_spec("PyQt6.uic") is None: print("Sorry, {0} is not installed.".format(pyuic)) exit(1) print("Found {0}".format(pyuic)) if importlib.util.find_spec("PyQt6.QtWebEngineWidgets") is None: if isSudo: print("Optional 'PyQt6-WebEngine' could not be detected.") else: msg = "Optional 'PyQt6-WebEngine' could not be detected." pipInstall( "PyQt6-WebEngine>={0}".format( versionToStr(requiredVersions["pyqt6-webengine"]) ), msg, ) print("Found PyQt6-WebEngine") if importlib.util.find_spec("PyQt6.QtCharts") is None: if isSudo: print("Optional 'PyQt6-Charts' could not be detected.") else: msg = "Optional 'PyQt6-Charts' could not be detected." pipInstall( "PyQt6-Charts>={0}".format( versionToStr(requiredVersions["pyqt6-charts"]) ), msg, ) print("Found PyQt6-Charts") if importlib.util.find_spec("PyQt6.Qsci") is None: msg = "'PyQt6-QScintilla' could not be detected." installed = not isSudo and pipInstall( "PyQt6-QScintilla>={0}".format( versionToStr(requiredVersions["pyqt6-qscintilla"]) ), msg, ) message = None if installed else "PyQt6-QScintilla could not be installed." if message: print("Sorry, please install QScintilla2 and") print("its PyQt6 wrapper.") print("Error: {0}".format(message)) exit(1) print("Found PyQt6-QScintilla") pyqt6BaseModulesList = [ "PyQt6.QtGui", "PyQt6.QtNetwork", "PyQt6.QtPrintSupport", "PyQt6.QtSql", "PyQt6.QtSvg", "PyQt6.QtSvgWidgets", "PyQt6.QtWidgets", ] requiredModulesList = { # key is pip project name # value is tuple of package name, pip install constraint "tomlkit": ("tomlkit", ""), "asttokens": ("asttokens", ""), "EditorConfig": ("editorconfig", ""), "Pygments": ("pygments", ""), "parso": ("parso", ""), "jedi": ("jedi", ""), "packaging": ("packaging", ""), "cyclonedx-python-lib": ("cyclonedx", ""), "cyclonedx-bom": ("cyclonedx_py", ""), "trove-classifiers": ("trove_classifiers", ""), "black": ("black", ">=22.6.0"), "isort": ("isort", ">=5.10.0"), "coverage": ("coverage", ">=6.5.0"), "semver": ("semver", ""), "pipdeptree": ("pipdeptree", ""), } optionalModulesList = { # key is pip project name # value is tuple of package name, pip install constraint "docutils": ("docutils", ""), "Markdown": ("markdown", ""), "pyyaml": ("yaml", ""), "chardet": ("chardet", ""), "pyenchant": ("enchant", ""), "wheel": ("wheel", ""), "esprima": ("esprima", ""), } if withPyqt6Tools: optionalModulesList["qt6-applications"] = ("qt6_applications", "") # check mandatory PyQt6 modules modulesOK = True for pyqt6BaseModule in sorted(pyqt6BaseModulesList): name = pyqt6BaseModule.split(".")[1] try: importlib.import_module(pyqt6BaseModule) print("Found", name) except ImportError as err: print("Sorry, please install {0}.".format(name)) if verbose: print("Error: {0}".format(err)) modulesOK = False if not modulesOK: exit(1) # check required modules print("\nRequired Packages") print("-----------------") requiredMissing = False for requiredPackage in sorted(requiredModulesList): try: importlib.import_module(requiredModulesList[requiredPackage][0]) print("Found", requiredPackage) except ImportError as err: if isSudo: print("Required '{0}' could not be detected.".format(requiredPackage)) requiredMissing = True else: msg = "Required '{0}' could not be detected.{1}".format( requiredPackage, "\nError: {0}".format(err) if verbose else "" ) pipInstall( requiredPackage + requiredModulesList[requiredPackage][1], msg, force=True, ) if requiredMissing: print("Some required packages are missing and could not be installed.") print("Install them manually with:") print(" {0} install-dependencies.py --required".format(sys.executable)) # check optional modules print("\nOptional Packages") print("-----------------") optionalMissing = False for optPackage in sorted(optionalModulesList): try: importlib.import_module(optionalModulesList[optPackage][0]) print("Found", optPackage) except ImportError as err: if isSudo: print("Optional '{0}' could not be detected.".format(optPackage)) optionalMissing = True else: msg = "Optional '{0}' could not be detected.{1}".format( optPackage, "\nError: {0}".format(err) if verbose else "" ) pipInstall(optPackage + optionalModulesList[optPackage][1], msg) if optionalMissing: print("Some optional packages are missing and could not be installed.") print("Install them manually with:") print(" {0} install-dependencies.py --optional".format(sys.executable)) if requiredMissing and optionalMissing: print("Alternatively you may install all of them with:") print(" {0} install-dependencies.py --all".format(sys.executable)) # determine the platform dependent black list if sys.platform.startswith(("win", "cygwin")): PlatformBlackLists = PlatformsBlackLists["windows"] elif sys.platform.startswith("linux"): PlatformBlackLists = PlatformsBlackLists["linux"] elif sys.platform.startswith("freebsd"): PlatformBlackLists = PlatformsBlackLists["freebsd"] else: PlatformBlackLists = PlatformsBlackLists["mac"] print("\nVersion Information") print("-------------------") print("Python: {0:d}.{1:d}.{2:d}".format(*sys.version_info[:3])) # check version of Qt # =================== qtMajor = int(qVersion().split(".")[0]) qtMinor = int(qVersion().split(".")[1]) print("Qt6: {0}".format(qVersion().strip())) if qtMajor == 6 and qtMinor < 1: print("Sorry, you must have Qt version 6.1.0 or better.") exit(2) # check version of sip # ==================== with contextlib.suppress(ImportError, AttributeError): try: from PyQt6 import sip # noqa: I101, I102 except ImportError: import sip # noqa: I101, I102 print("sip:", sip.SIP_VERSION_STR.strip()) # always assume, that snapshots or dev versions are new enough if "snapshot" not in sip.SIP_VERSION_STR and "dev" not in sip.SIP_VERSION_STR: if sip.SIP_VERSION < requiredVersions["sip"]: print( "Sorry, you must have sip {0} or higher or" " a recent development release.".format( versionToStr(requiredVersions["sip"]) ) ) exit(3) # check for blacklisted versions for vers in BlackLists["sip"] + PlatformBlackLists["sip"]: if vers == sip.SIP_VERSION: print( "Sorry, sip version {0} is not compatible with eric.".format( versionToStr(vers) ) ) print("Please install another version.") exit(3) # check version of PyQt6 # ====================== from PyQt6.QtCore import PYQT_VERSION, PYQT_VERSION_STR # noqa: I101, I102 print("PyQt6:", PYQT_VERSION_STR.strip()) # always assume, that snapshots or dev versions are new enough if "snapshot" not in PYQT_VERSION_STR and "dev" not in PYQT_VERSION_STR: if PYQT_VERSION < requiredVersions["pyqt6"]: print( "Sorry, you must have PyQt {0} or better or" " a recent development release.".format( versionToStr(requiredVersions["pyqt6"]) ) ) exit(4) # check for blacklisted versions for vers in BlackLists["PyQt6"] + PlatformBlackLists["PyQt6"]: if vers == PYQT_VERSION: print( "Sorry, PyQt version {0} is not compatible with eric.".format( versionToStr(vers) ) ) print("Please install another version.") exit(4) # check version of QScintilla # =========================== from PyQt6.Qsci import ( # noqa: I101, I102 QSCINTILLA_VERSION, QSCINTILLA_VERSION_STR, ) print("PyQt6-QScintilla:", QSCINTILLA_VERSION_STR.strip()) # always assume, that snapshots or dev versions are new enough if "snapshot" not in QSCINTILLA_VERSION_STR and "dev" not in QSCINTILLA_VERSION_STR: if QSCINTILLA_VERSION < requiredVersions["pyqt6-qscintilla"]: print( "Sorry, you must have PyQt6-QScintilla {0} or higher or" " a recent development release.".format( versionToStr(requiredVersions["pyqt6-qscintilla"]) ) ) exit(5) # check for blacklisted versions for vers in BlackLists["QScintilla2"] + PlatformBlackLists["QScintilla2"]: if vers == QSCINTILLA_VERSION: print( "Sorry, QScintilla2 version {0} is not compatible with" " eric.".format(versionToStr(vers)) ) print("Please install another version.") exit(5) # print version info for additional modules with contextlib.suppress(NameError, AttributeError): from PyQt6.QtCharts import PYQT_CHART_VERSION_STR # noqa: I101, I102 print("PyQt6-Charts:", PYQT_CHART_VERSION_STR) with contextlib.suppress(ImportError, AttributeError): from PyQt6 import QtWebEngineCore # noqa: I101, I102 print("PyQt6-WebEngine:", QtWebEngineCore.PYQT_WEBENGINE_VERSION_STR) print() print("All dependencies ok.") print() 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 __compileOneUi(ui_path, mapFunc=None): """ Function to compile a single form file to Python code. @param ui_path path of the Qt form file @type str @param mapFunc function to change directory and/or name of the resulting Python file (defaults to None) @type func (optional) """ from PyQt6.uic import compileUi # noqa: I102 py_dir, py_file = os.path.split(ui_path[:-3] + ".py") # Allow the caller to change the name of the .py file or generate # it in a different directory. if mapFunc is not None: py_dir, py_file = mapFunc(py_dir, py_file) # Make sure the destination directory exists. os.makedirs(py_dir, exist_ok=True) py_path = os.path.join(py_dir, py_file) with open(py_path, "w", encoding="utf-8") as py_file: compileUi(ui_path, py_file, execute=False, indent=4) def compileUiDir(root, recurse=False, mapFunc=None, workers=1): """ Function to compile all Qt form files of a directory or directory tree to Python code. @param root directory to scan for Qt form files (i.e. files ending with '.ui' @type str @param recurse flag indicating to recurse into sub-directories (defaults to False) @type bool (optional) @param mapFunc function to change directory and/or name of the resulting Python file (defaults to None) @type func (optional) @param workers number of worker processes to be used to compile (defaults to 1) @type int (optional) """ if recurse: ui_files = [] for rootDir, _, files in os.walk(root): ui_files.extend( os.path.join(rootDir, ui) for ui in files if ui.endswith(".ui") ) else: ui_files = [ os.path.join(root, ui) for ui in os.listdir(root) if os.path.isfile(os.path.join(root, ui) and ui.endswith(".ui")) ] ProcessPoolExecutor = None if workers != 1: try: from concurrent.futures import ProcessPoolExecutor # noqa: I101, I103 except NotImplementedError: workers = 1 if workers != 1 and ProcessPoolExecutor is not None: # If workers == 0, let ProcessPoolExecutor determine worker count. workers = workers or None with ProcessPoolExecutor(max_workers=workers) as executor: executor.map( partial(__compileOneUi, mapFunc=mapFunc), ui_files, ) else: for ui_file in ui_files: __compileOneUi(ui_file, mapFunc=mapFunc) def prepareInfoFile(fileName): """ Function to prepare an Info.py file when installing from source. @param fileName name of the Python file containing the info (string) """ if not fileName: return with contextlib.suppress(OSError): os.rename(fileName, fileName + ".orig") localHg = ( os.path.join(sysconfig.get_path("scripts"), "hg.exe") if sys.platform.startswith(("win", "cygwin")) else os.path.join(sysconfig.get_path("scripts"), "hg") ) for hg in (localHg, "hg"): with contextlib.suppress(OSError, subprocess.CalledProcessError): hgOut = subprocess.run( # secok [hg, "identify", "-i"], check=True, capture_output=True, text=True ).stdout if hgOut: break else: hgOut = "" if hgOut: hgOut = hgOut.strip() if hgOut.endswith("+"): hgOut = hgOut[:-1] with open(fileName + ".orig", "r", encoding="utf-8") as f: text = f.read() text = text.replace("@@REVISION@@", hgOut).replace( "@@VERSION@@", "rev_" + hgOut ) copyToFile(fileName, text) else: shutil.copy(fileName + ".orig", fileName) 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 # noqa: I101, I103 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 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 pywintypes import com_error # noqa: I102 from win32com.client import Dispatch # noqa: I102 with contextlib.suppress(com_error): shell = Dispatch("WScript.Shell") shortcut = shell.CreateShortCut(linkPath) shortcut.Targetpath = targetPath shortcut.WorkingDirectory = os.path.dirname(targetPath) shortcut.IconLocation = iconPath shortcut.save() def windowsDesktopNames(clean=False): """ Function to generate the link names for the Windows Desktop. @param clean flag indicating to get the desktop names for a cleanup operation @type bool @return list of desktop link names @rtype list of str """ return [e[0] for e in windowsDesktopEntries(clean=clean)] def windowsDesktopEntries(clean=False): """ Function to generate data for the Windows Desktop links. @param clean flag indicating to get the desktop entries for a cleanup operation @type bool @return list of tuples containing the desktop link name, the link target and the icon target @rtype list of tuples of (str, str, str) """ global cfg majorVersion, minorVersion = sys.version_info[:2] entriesTemplates = [ ( "eric7 IDE (Python {0}.{1}).lnk", os.path.join(cfg["bindir"], "eric7_ide.cmd"), os.path.join(cfg["ericPixDir"], "eric7.ico"), ), ( "eric7 Browser (Python {0}.{1}).lnk", os.path.join(cfg["bindir"], "eric7_browser.cmd"), os.path.join(cfg["ericPixDir"], "ericWeb48.ico"), ), ] if clean: # clean obsolete entries as well entriesTemplates.extend( [ ( "eric7 (Python {0}.{1}).lnk", os.path.join(cfg["bindir"], "eric7_ide.cmd"), os.path.join(cfg["ericPixDir"], "eric7.ico"), ), ] ) return [ (e[0].format(majorVersion, minorVersion), e[1], e[2]) for e in entriesTemplates ] 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 "eric7 (Python {0}.{1})".format(majorVersion, minorVersion) def createArgumentParser(): """ Function to create an argument parser. @return created argument parser object @rtype argparse.ArgumentParser """ parser = argparse.ArgumentParser( description="Install eric7 from the source code tree.", epilog="The file given to the -f option must be valid Python code defining a" "dictionary called 'cfg' with the keys 'ericDir', 'ericPixDir', ericIconDir'," " 'ericDTDDir', 'ericCSSDir', 'ericStylesDir', 'ericThemesDir', ericDocDir'," " ericExamplesDir', ericTranslationsDir', 'ericTemplatesDir'," " 'ericCodeTemplatesDir', ericOthersDir','bindir', 'mdir' and 'apidir." "These define the directories for the installation of the various parts of" " eric.", ) parser.add_argument( "-a", metavar="dir", default=apisDir if apisDir else None, help="directory where the API files will be installed ({0})".format( "default: {0}".format(apisDir) if apisDir else "no default value" ), ) parser.add_argument( "--no-apis", action="store_true", help="don't install API files", ) parser.add_argument( "-b", metavar="dir", default=platBinDir, help="directory where the binaries will be installed (default: {0})".format( platBinDir ), ) parser.add_argument( "-d", metavar="dir", default=modDir, help="directory where eric Python files will be installed" " (default: {0})".format(modDir), ) parser.add_argument( "-f", metavar="file", help="configuration file naming the various installation paths", ) if not sys.platform.startswith(("win", "cygwin")): parser.add_argument( "-i", metavar="dir", default=distDir, help="temporary install prefix (default: {0})".format(distDir), ) if sys.platform == "darwin": parser.add_argument( "-m", metavar="name", default=macAppBundleName, help="name of the Mac app bundle (default: {0})".format(macAppBundleName), ) parser.add_argument( "-n", metavar="path", default=macAppBundlePath, help="path of the directory the Mac app bundle will be created in" " (default: {0})".format(macAppBundlePath), ) parser.add_argument( "-p", metavar="python", default=macPythonExe, help="path of the python executable (default: {0})".format(macPythonExe), ) parser.add_argument( "-c", action="store_false", help="don't cleanup old installation first", ) parser.add_argument( "-v", "--verbose", action="store_true", help="print some more information", ) parser.add_argument( "-x", action="store_false", help="don't perform dependency checks (use on your own risk)", ) parser.add_argument( "-z", action="store_false", help="don't compile the installed python files", ) parser.add_argument( "--yes", action="store_true", help="answer 'yes' to all questions", ) if sys.platform.startswith(("win", "cygwin")): parser.add_argument( "--clean-desktop", action="store_true", help="delete desktop links before installation", ) parser.add_argument( "--no-info", action="store_true", help="don't create the install info file", ) parser.add_argument( "--with-tools", action="store_true", help="install the 'qt6-applications' package", ) return parser def main(argv): """ The main function of the script. @param argv list of command line arguments @type list of str """ global modDir, doCleanup, doCompile, distDir, cfg, apisDir global sourceDir, eric7SourceDir, configName, platBinDir global macAppBundlePath, macAppBundleName, macPythonExe global installApis, doCleanDesktopLinks, yes2All global createInstallInfoFile, installCwd global withPyqt6Tools global verbose if sys.version_info < (3, 8, 0) or sys.version_info > (3, 99, 99): print("Sorry, eric requires at least Python 3.8 for running.") exit(5) installCwd = os.getcwd() if os.path.dirname(argv[0]): os.chdir(os.path.dirname(argv[0])) initGlobals() parser = createArgumentParser() args = parser.parse_args() apisDir = args.a platBinDir = args.b modDir = args.d depChecks = args.x doCleanup = args.c doCompile = args.z installApis = not args.no_apis yes2All = args.yes withPyqt6Tools = args.with_tools createInstallInfoFile = not args.no_info verbose = args.verbose if sys.platform == "darwin": macAppBundleName = args.m macAppBundlePath = args.n macPythonExe = args.p if sys.platform.startswith(("win", "cygwin")): doCleanDesktopLinks = args.clean_desktop else: if args.i: distDir = os.path.normpath(args.i) if args.f: with open(args.f) as f: try: exec(compile(f.read(), args.f, "exec"), globals()) # secok if len(cfg) != configLength: print( "The configuration dictionary in '{0}' is" " incorrect. Aborting".format(args.f) ) exit(6) except Exception as exc: print( "The configuration file '{0}' is not valid Python source." " It will be ignored. Installation will be performed with" " defaults.".format(args.f) ) print("ERROR: {0}".format(str(exc))) cfg = {} infoName = "" installFromSource = not os.path.isdir(sourceDir) # check dependencies if depChecks: doDependancyChecks() if installFromSource: sourceDir = os.path.abspath("..") eric7SourceDir = ( os.path.join(sourceDir, "src", "eric7") if os.path.exists(os.path.join(sourceDir, "src", "eric7")) else os.path.join(sourceDir, "eric7") ) # cleanup source if installing from source if installFromSource: print("Cleaning up source ...", end="", flush=True) cleanupSource(sourceDir) print(" Done") configName = os.path.join(eric7SourceDir, "eric7config.py") if os.path.exists(os.path.join(sourceDir, ".hg")): # we are installing from source with repo infoName = os.path.join(eric7SourceDir, "UI", "Info.py") prepareInfoFile(infoName) if len(cfg) == 0: createInstallConfig() # get rid of development config file, if it exists with contextlib.suppress(OSError): if installFromSource: os.rename(configName, configName + ".orig") configNameC = configName + "c" if os.path.exists(configNameC): os.remove(configNameC) os.remove(configName) # cleanup old installation print("Cleaning up old installation ...", end="", flush=True) try: if doCleanup: if distDir: shutil.rmtree(distDir, True) else: cleanUp() except OSError as msg: sys.stderr.write("\nError: {0}\nTry install as root.\n".format(msg)) exit(7) print(" Done") # Create a config file and delete the default one print("Creating configuration file ...", end="", flush=True) createConfig() print(" Done") # Create an install info file print("Creating an install info file ...", end="", flush=True) createInstallInfo() print(" Done") # Compile .ui files print("Compiling user interface files ...", end="", flush=True) # step 1: remove old Ui_*.py files for root, _, files in os.walk(sourceDir): for file in [f for f in files if fnmatch.fnmatch(f, "Ui_*.py")]: os.remove(os.path.join(root, file)) # step 2: compile the forms compileUiDir(eric7SourceDir, recurse=True, mapFunc=__pyName, workers=0) print(" Done") if doCompile: print("Compiling source files ...", end="", flush=True) skipRe = re.compile(r"DebugClients[\\/]Python[\\/]") sys.stdout = io.StringIO() if distDir: compileall.compile_dir( eric7SourceDir, ddir=os.path.join(distDir, modDir, cfg["ericDir"]), rx=skipRe, quiet=True, workers=0, ) py_compile.compile( configName, dfile=os.path.join(distDir, modDir, "eric7config.py") ) else: compileall.compile_dir( eric7SourceDir, ddir=os.path.join(modDir, cfg["ericDir"]), rx=skipRe, quiet=True, workers=0, ) py_compile.compile(configName, dfile=os.path.join(modDir, "eric7config.py")) sys.stdout = sys.__stdout__ print(" Done") print("Installing eric ...", end="", flush=True) res = installEric() print(" Done") if createInstallInfoFile: with open( os.path.join(cfg["ericDir"], installInfoName), "w" ) as installInfoFile: json.dump(installInfo, installInfoFile, indent=2) # do some cleanup with contextlib.suppress(OSError): if installFromSource: os.remove(configName) configNameC = configName + "c" if os.path.exists(configNameC): os.remove(configNameC) os.rename(configName + ".orig", configName) with contextlib.suppress(OSError): if installFromSource and infoName: os.remove(infoName) infoNameC = infoName + "c" if os.path.exists(infoNameC): os.remove(infoNameC) os.rename(infoName + ".orig", infoName) print("\nInstallation complete.") print() exit(res) if __name__ == "__main__": try: main(sys.argv) except SystemExit: raise except Exception: print( """An internal error occured. Please report all the output""" """ of the program,\nincluding the following traceback, to""" """ eric-bugs@eric-ide.python-projects.org.\n""" ) raise # # eflag: noqa = M801