scripts/install-debugclients.py

branch
eric7
changeset 9690
2dc33116df50
parent 9674
43dd357b3bff
child 9694
90a7081e2837
--- 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)

eric ide

mercurial