diff -r b18ad1f984b1 -r 2dc33116df50 scripts/install-debugclients.py --- a/scripts/install-debugclients.py Tue Jan 10 13:11:52 2023 +0100 +++ b/scripts/install-debugclients.py Wed Jan 11 15:03:59 2023 +0100 @@ -15,10 +15,13 @@ import contextlib import fnmatch import getopt +import importlib import io +import json import os import re import shutil +import subprocess import sys import sysconfig @@ -28,7 +31,7 @@ modDir = None pyModDir = None distDir = None -installPackage = "eric7DebugClients" +installPackage = "eric7" doCleanup = True doCompile = True sourceDir = "eric" @@ -206,21 +209,178 @@ # copy the various parts of eric debug clients copyTree( os.path.join(eric7SourceDir, "DebugClients"), - targetDir, + 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("Error: {0}\nTry install with admin rights.\n".format(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, 7, 0) or sys.version_info >= (3, 12, 0): + print("Sorry, you must have Python 3.7.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. @@ -265,6 +425,9 @@ elif opt == "-z": doCompile = False + # check dependencies + doDependancyChecks() + installFromSource = not os.path.isdir(sourceDir) if installFromSource: sourceDir = os.path.abspath("..") @@ -277,24 +440,25 @@ # cleanup source if installing from source if installFromSource: - print("Cleaning up source ...") + print("Cleaning up source ...", end="") cleanupSource(os.path.join(eric7SourceDir, "DebugClients")) - print() + print(" Done") # cleanup old installation try: if doCleanup: - print("Cleaning up old installation ...") + print("Cleaning up old installation ...", end="") 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("\nCompiling source files ...") + print("Compiling source files ...", end="") skipRe = re.compile(r"DebugClients[\\/]Python[\\/]") sys.stdout = io.StringIO() if distDir: @@ -312,10 +476,13 @@ quiet=True, ) sys.stdout = sys.__stdout__ - print("\nInstalling eric debug clients ...") + print(" Done") + + print("Installing eric debug clients ...", end="") res = installEricDebugClients() + print(" Done") - print("\nInstallation complete.") + print("Installation complete.") print() exit(res)