Thu, 20 Jul 2023 11:00:30 +0200
Removed support for Python 3.7 because that is EOL.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (c) 2016 - 2023 Detlev Offenbach <detlev@die-offenbachs.de> # # This is the install script for the eric debug client. It may be used # to just install the debug clients for remote debugging. # """ Installation script for the eric debug clients. """ import compileall import contextlib import fnmatch import getopt import importlib import io import json import os import re import shutil import subprocess import sys import sysconfig # Define the globals. progName = None currDir = os.getcwd() modDir = None pyModDir = None distDir = None installPackage = "eric7" doCleanup = True doCompile = True sourceDir = "eric" eric7SourceDir = "" def exit(rcode=0): """ Exit the install script. @param rcode result code to report back (integer) """ global currDir if sys.platform.startswith("win"): with contextlib.suppress(EOFError): input("Press enter to continue...") # secok os.chdir(currDir) sys.exit(rcode) def usage(rcode=2): """ Display a usage message and exit. @param rcode the return code passed back to the calling process. """ global progName, modDir, distDir print() print("Usage:") if sys.platform == "darwin": print(" {0} [-chz] [-d dir] [-i dir]".format(progName)) elif sys.platform.startswith("win"): print(" {0} [-chz] [-d dir]".format(progName)) else: print(" {0} [-chz][-d dir] [-i dir]".format(progName)) print("where:") print(" -h, --help display this help message") print(" -d dir where eric debug client files will be installed") print(" (default: {0})".format(modDir)) if not sys.platform.startswith("win"): print(" -i dir temporary install prefix") print(" (default: {0})".format(distDir)) print(" -c don't cleanup old installation first") print(" -z don't compile the installed python files") exit(rcode) def initGlobals(): """ Module function to set the values of globals that need more than a simple assignment. """ global modDir, pyModDir 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 def copyTree(src, dst, filters, excludeDirs=None, excludePatterns=None): """ Copy files 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 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 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 the __pycache__ directory and all *.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 2: 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 debug client files. """ global pyModDir, installPackage try: # Cleanup the install directories dirname = os.path.join(pyModDir, installPackage) if os.path.exists(dirname): shutil.rmtree(dirname, True) except OSError as msg: sys.stderr.write("Error: {0}\nTry install with admin rights.\n".format(msg)) exit(7) 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 installEricDebugClients(): """ Actually perform the installation steps. @return result code (integer) """ global distDir, doCleanup, sourceDir, modDir # set install prefix, if not None targetDir = ( os.path.normpath(os.path.join(distDir, installPackage)) if distDir else os.path.join(modDir, installPackage) ) try: # Install the files # copy the various parts of eric debug clients copyTree( os.path.join(eric7SourceDir, "DebugClients"), os.path.join(targetDir, "DebugClients"), ["*.py", "*.pyc", "*.pyo", "*.pyw"], excludePatterns=["eric7config.py*"], ) # copy the top level package file shutilCopy(os.path.join(eric7SourceDir, "__init__.py"), targetDir) # copy the license file shutilCopy(os.path.join(sourceDir, "docs", "LICENSE.txt"), targetDir) except OSError as msg: sys.stderr.write("\nError: {0}\nTry install with admin rights.\n".format(msg)) return 7 return 0 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 """ ok = False if 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 doDependancyChecks(): """ Perform some dependency checks. """ try: isSudo = os.getuid() == 0 and sys.platform != "darwin" # 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, 12, 0): print("Sorry, you must have Python 3.8.0 or higher, but less 3.12.0.") print("Yours is {0}.".format(".".join(str(v) for v in sys.version_info[:3]))) exit(5) requiredModulesList = { # key is pip project name # value is tuple of package name, pip install constraint "coverage": ("coverage", ">=6.5.0"), } # check required modules print("Required 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: pipInstall( requiredPackage + requiredModulesList[requiredPackage][1], "Required '{0}' could not be detected.{1}".format( requiredPackage, "\nError: {0}".format(err) ), force=True, ) if requiredMissing: print("Some required packages are missing and could not be installed.") print("Install them manually.") print() print("All dependencies ok.") print() def main(argv): """ The main function of the script. @param argv the list of command line arguments. """ # Parse the command line. global progName, modDir, doCleanup, doCompile, distDir global sourceDir, eric7SourceDir if sys.version_info < (3, 8, 0) or sys.version_info >= (4, 0, 0): print("Sorry, the eric debugger requires Python 3.8 or better for running.") exit(5) progName = os.path.basename(argv[0]) if os.path.dirname(argv[0]): os.chdir(os.path.dirname(argv[0])) initGlobals() try: if sys.platform.startswith("win"): optlist, args = getopt.getopt(argv[1:], "chzd:", ["help"]) elif sys.platform == "darwin": optlist, args = getopt.getopt(argv[1:], "chzd:i:", ["help"]) else: optlist, args = getopt.getopt(argv[1:], "chzd:i:", ["help"]) except getopt.GetoptError as err: print(err) usage() for opt, arg in optlist: if opt in ["-h", "--help"]: usage(0) elif opt == "-d": modDir = arg elif opt == "-i": distDir = os.path.normpath(arg) elif opt == "-c": doCleanup = False elif opt == "-z": doCompile = False # check dependencies doDependancyChecks() installFromSource = not os.path.isdir(sourceDir) if installFromSource: sourceDir = os.path.abspath("..") eric7SourceDir = ( os.path.join(sourceDir, "eric7") if os.path.exists(os.path.join(sourceDir, "eric7")) else os.path.join(sourceDir, "src", "eric7") ) # cleanup source if installing from source if installFromSource: print("Cleaning up source ...", end="", flush=True) cleanupSource(os.path.join(eric7SourceDir, "DebugClients")) print(" Done") # cleanup old installation try: if doCleanup: print("Cleaning up old installation ...", end="", flush=True) if distDir: shutil.rmtree(distDir, True) else: cleanUp() print(" Done") except OSError as msg: sys.stderr.write("Error: {0}\nTry install as root.\n".format(msg)) exit(7) if doCompile: print("Compiling source files ...", end="", flush=True) skipRe = re.compile(r"DebugClients[\\/]Python[\\/]") sys.stdout = io.StringIO() if distDir: compileall.compile_dir( os.path.join(eric7SourceDir, "DebugClients"), ddir=os.path.join(distDir, modDir, installPackage), rx=skipRe, quiet=True, ) else: compileall.compile_dir( os.path.join(eric7SourceDir, "DebugClients"), ddir=os.path.join(modDir, installPackage), rx=skipRe, quiet=True, ) sys.stdout = sys.__stdout__ print(" Done") print("Installing eric debug clients ...", end="", flush=True) res = installEricDebugClients() print(" Done") print("Installation 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