scripts/install.py

branch
eric7-maintenance
changeset 10349
df7edc29cbfb
parent 10272
7ae72d1df070
parent 10341
3fdffd9cc21d
child 10460
3b34efa2857c
--- a/scripts/install.py	Tue Oct 31 09:23:05 2023 +0100
+++ b/scripts/install.py	Wed Nov 29 14:23:36 2023 +0100
@@ -9,11 +9,11 @@
 Installation script for the eric IDE and all eric related tools.
 """
 
+import argparse
 import compileall
 import contextlib
 import datetime
 import fnmatch
-import getopt
 import getpass
 import glob
 import importlib
@@ -32,7 +32,6 @@
 from functools import partial
 
 # Define the globals.
-progName = None
 currDir = os.getcwd()
 modDir = None
 pyModDir = None
@@ -76,7 +75,7 @@
     "QScintilla2": [],
 }
 PlatformsBlackLists = {
-    "windows": {
+    "freebsd": {
         "sip": [],
         "PyQt6": [],
         "QScintilla2": [],
@@ -91,6 +90,11 @@
         "PyQt6": [],
         "QScintilla2": [],
     },
+    "windows": {
+        "sip": [],
+        "PyQt6": [],
+        "QScintilla2": [],
+    },
 }
 
 
@@ -113,87 +117,6 @@
     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, apisDir
-    global macAppBundleName, macAppBundlePath, macPythonExe
-
-    print()
-    print("Usage:")
-    if sys.platform == "darwin":
-        print(
-            "    {0} [-chvxz] [-a dir] [-b dir] [-d dir] [-f file] [-i dir]"
-            " [-m name] [-n path] [-p python] [--help] [--no-apis]"
-            " [--no-info] [--with-tools] [--verbose] [--yes]".format(progName)
-        )
-    elif sys.platform.startswith(("win", "cygwin")):
-        print(
-            "    {0} [-chvxz] [-a dir] [-b dir] [-d dir] [-f file]"
-            " [--clean-desktop] [--help] [--no-apis] [--no-info]"
-            " [--with-tools] [--verbose] [--yes]".format(progName)
-        )
-    else:
-        print(
-            "    {0} [-chvxz] [-a dir] [-b dir] [-d dir] [-f file] [-i dir]"
-            " [--help] [--no-apis] [--no-info] [--with-tools] [--verbose]"
-            " [--yes]".format(progName)
-        )
-    print("where:")
-    print("    -h, --help display this help message")
-    print("    -a dir     where the API files will be installed")
-    if apisDir:
-        print("               (default: {0})".format(apisDir))
-    else:
-        print("               (no default value)")
-    print("    --no-apis  don't install API files")
-    print("    -b dir     where the binaries will be installed")
-    print("               (default: {0})".format(platBinDir))
-    print("    -d dir     where eric python files will be installed")
-    print("               (default: {0})".format(modDir))
-    print("    -f file    configuration file naming the various installation paths")
-    if not sys.platform.startswith(("win", "cygwin")):
-        print("    -i dir     temporary install prefix")
-        print("               (default: {0})".format(distDir))
-    if sys.platform == "darwin":
-        print("    -m name    name of the Mac app bundle")
-        print("               (default: {0})".format(macAppBundleName))
-        print("    -n path    path of the directory the Mac app bundle will")
-        print("               be created in")
-        print("               (default: {0})".format(macAppBundlePath))
-        print("    -p python  path of the python executable")
-        print("               (default: {0})".format(macPythonExe))
-    print("    -c         don't cleanup old installation first")
-    print("    -v, --verbose print some more information")
-    print("    -x         don't perform dependency checks (use on your own risk)")
-    print("    -z         don't compile the installed python files")
-    print("    --yes      answer 'yes' to all questions")
-    print()
-    if sys.platform.startswith(("win", "cygwin")):
-        print("    --clean-desktop delete desktop links before installation")
-    print("    --no-info  don't create the install info file")
-    print("    --with-tools install qt6-applications")
-    print()
-    print("The file given to the -f option must be valid Python code defining a")
-    print(
-        "dictionary called 'cfg' with the keys 'ericDir', 'ericPixDir',"
-        " 'ericIconDir',"
-    )
-    print("'ericDTDDir', 'ericCSSDir', 'ericStylesDir', 'ericThemesDir',")
-    print(" 'ericDocDir', ericExamplesDir',")
-    print("'ericTranslationsDir', 'ericTemplatesDir', 'ericCodeTemplatesDir',")
-    print("'ericOthersDir','bindir', 'mdir' and 'apidir.")
-    print(
-        "These define the directories for the installation of the various"
-        " parts of eric."
-    )
-
-    exit(rcode)
-
-
 def initGlobals():
     """
     Module function to set the values of globals that need more than a
@@ -513,7 +436,7 @@
     global pyModDir, progLanguages
 
     # Remove the menu entry for Linux systems
-    if sys.platform.startswith("linux"):
+    if sys.platform.startswith(("linux", "freebsd")):
         cleanUpLinuxSpecifics()
     # Remove the Desktop and Start Menu entries for Windows systems
     elif sys.platform.startswith(("win", "cygwin")):
@@ -677,9 +600,7 @@
         macAppBundleName = getConfig("macAppBundleName")
     except AttributeError:
         macAppBundlePath = (
-            defaultMacAppBundlePath
-            if os.getpid() == 0
-            else defaultMacUserAppBundlePath
+            defaultMacAppBundlePath if os.getpid() == 0 else defaultMacUserAppBundlePath
         )
         macAppBundleName = defaultMacAppBundleName
     for bundlePath in [
@@ -695,14 +616,12 @@
     """
     Clean up the Desktop and Start Menu entries for Windows.
     """
-    global doCleanDesktopLinks, forceCleanDesktopLinks
-
-    try:
-        from pywintypes import com_error  # __IGNORE_WARNING__
-    except ImportError:
+    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"
@@ -971,7 +890,7 @@
             print("Use the API files provided by the 'API Files' plug-in.")
 
     # Create menu entry for Linux systems
-    if sys.platform.startswith("linux"):
+    if sys.platform.startswith(("linux", "freebsd")):
         createLinuxSpecifics()
 
     # Create Desktop and Start Menu entries for Windows systems
@@ -1129,10 +1048,7 @@
     """
     Create Desktop and Start Menu links.
     """
-    try:
-        # check, if pywin32 is available
-        from win32com.client import Dispatch  # __IGNORE_WARNING__
-    except (ImportError, ModuleNotFoundError):  # noqa: M514
+    if importlib.util.find_spec("win32com") is None:
         installed = pipInstall(
             "pywin32",
             "\nThe Python package 'pywin32' could not be imported.",
@@ -1609,9 +1525,10 @@
         print("Yours is {0}.".format(".".join(str(v) for v in sys.version_info[:3])))
         exit(5)
 
-    try:
-        import xml.etree  # __IGNORE_WARNING__
-    except ImportError:
+    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)
@@ -1641,25 +1558,17 @@
             exit(1)
     print("Found PyQt6")
 
-    try:
-        pyuic = "pyuic6"
-        from PyQt6 import uic  # __IGNORE_WARNING__
-    except ImportError as err:
+    pyuic = "pyuic6"
+    if importlib.util.find_spec("PyQt6.uic") is None:
         print("Sorry, {0} is not installed.".format(pyuic))
-        if verbose:
-            print("Error: {0}".format(err))
         exit(1)
     print("Found {0}".format(pyuic))
 
-    try:
-        from PyQt6 import QtWebEngineWidgets  # __IGNORE_WARNING__
-    except ImportError as err:
+    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.{0}".format(
-                "\nError: {0}".format(err) if verbose else ""
-            )
+            msg = "Optional 'PyQt6-WebEngine' could not be detected."
             pipInstall(
                 "PyQt6-WebEngine>={0}".format(
                     versionToStr(requiredVersions["pyqt6-webengine"])
@@ -1668,15 +1577,11 @@
             )
     print("Found PyQt6-WebEngine")
 
-    try:
-        from PyQt6 import QtCharts  # __IGNORE_WARNING__
-    except ImportError as err:
+    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.{0}".format(
-                "\nError: {0}".format(err) if verbose else ""
-            )
+            msg = "Optional 'PyQt6-Charts' could not be detected."
             pipInstall(
                 "PyQt6-Charts>={0}".format(
                     versionToStr(requiredVersions["pyqt6-charts"])
@@ -1685,28 +1590,15 @@
             )
     print("Found PyQt6-Charts")
 
-    try:
-        from PyQt6 import Qsci  # __IGNORE_WARNING__
-    except ImportError as err:
-        msg = "'PyQt6-QScintilla' could not be detected.{0}".format(
-            "\nError: {0}".format(err) if verbose else ""
-        )
+    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,
         )
-        if installed:
-            # try to import it again
-            try:
-                from PyQt6 import Qsci  # __IGNORE_WARNING__
-
-                message = None
-            except ImportError as msg:
-                message = str(msg)
-        else:
-            message = "PyQt6-QScintilla could not be installed."
+        message = None if installed else "PyQt6-QScintilla could not be installed."
         if message:
             print("Sorry, please install QScintilla2 and")
             print("its PyQt6 wrapper.")
@@ -1751,6 +1643,7 @@
         "chardet": ("chardet", ""),
         "pyenchant": ("enchant", ""),
         "wheel": ("wheel", ""),
+        "esprima": ("esprima", ""),
     }
     if withPyqt6Tools:
         optionalModulesList["qt6-applications"] = ("qt6_applications", "")
@@ -1827,6 +1720,8 @@
         PlatformBlackLists = PlatformsBlackLists["windows"]
     elif sys.platform.startswith("linux"):
         PlatformBlackLists = PlatformsBlackLists["linux"]
+    elif sys.platform.startswith("freebsd"):
+        PlatformBlackLists = PlatformsBlackLists["freebsd"]
     else:
         PlatformBlackLists = PlatformsBlackLists["mac"]
 
@@ -1929,7 +1824,9 @@
 
     # print version info for additional modules
     with contextlib.suppress(NameError, AttributeError):
-        print("PyQt6-Charts:", QtCharts.PYQT_CHART_VERSION_STR)
+        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
@@ -2012,7 +1909,7 @@
     ProcessPoolExecutor = None
     if workers != 1:
         try:
-            from concurrent.futures import ProcessPoolExecutor  # __IGNORE_WARNING__
+            from concurrent.futures import ProcessPoolExecutor  # noqa: I101, I103
         except NotImplementedError:
             workers = 1
 
@@ -2182,6 +2079,130 @@
     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.
@@ -2189,9 +2210,8 @@
     @param argv list of command line arguments
     @type list of str
     """
-    # Parse the command line.
-    global progName, modDir, doCleanup, doCompile, distDir, cfg, apisDir
-    global sourceDir, eric7SourceDir, configName
+    global modDir, doCleanup, doCompile, distDir, cfg, apisDir
+    global sourceDir, eric7SourceDir, configName, platBinDir
     global macAppBundlePath, macAppBundleName, macPythonExe
     global installApis, doCleanDesktopLinks, yes2All
     global createInstallInfoFile, installCwd
@@ -2202,8 +2222,6 @@
         print("Sorry, eric requires at least Python 3.8 for running.")
         exit(5)
 
-    progName = os.path.basename(argv[0])
-
     installCwd = os.getcwd()
 
     if os.path.dirname(argv[0]):
@@ -2211,95 +2229,48 @@
 
     initGlobals()
 
-    try:
-        if sys.platform.startswith(("win", "cygwin")):
-            optlist, args = getopt.getopt(
-                argv[1:],
-                "chvxza:b:d:f:",
-                [
-                    "clean-desktop",
-                    "help",
-                    "no-apis",
-                    "no-info",
-                    "verbose",
-                    "with-tools",
-                    "yes",
-                ],
-            )
-        elif sys.platform == "darwin":
-            optlist, args = getopt.getopt(
-                argv[1:],
-                "chvxza:b:d:f:i:m:n:p:",
-                ["help", "no-apis", "no-info", "with-tools", "verbose", "yes"],
-            )
-        else:
-            optlist, args = getopt.getopt(
-                argv[1:],
-                "chvxza:b:d:f:i:",
-                ["help", "no-apis", "no-info", "with-tools", "verbose", "yes"],
-            )
-    except getopt.GetoptError as err:
-        print(err)
-        usage()
-
-    global platBinDir
-
-    depChecks = True
+    parser = createArgumentParser()
+    args = parser.parse_args()
 
-    for opt, arg in optlist:
-        if opt in ["-h", "--help"]:
-            usage(0)
-        elif opt == "-a":
-            apisDir = arg
-        elif opt == "-b":
-            platBinDir = arg
-        elif opt == "-d":
-            modDir = arg
-        elif opt == "-i":
-            distDir = os.path.normpath(arg)
-        elif opt == "-x":
-            depChecks = False
-        elif opt == "-c":
-            doCleanup = False
-        elif opt == "-z":
-            doCompile = False
-        elif opt == "-f":
-            with open(arg) as f:
-                try:
-                    exec(compile(f.read(), arg, "exec"), globals())
-                    # secok
-                    if len(cfg) != configLength:
-                        print(
-                            "The configuration dictionary in '{0}' is"
-                            " incorrect. Aborting".format(arg)
-                        )
-                        exit(6)
-                except Exception as exc:
+    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 file '{0}' is not valid Python source."
-                        " It will be ignored. Installation will be performed with"
-                        " defaults.".format(arg)
+                        "The configuration dictionary in '{0}' is"
+                        " incorrect. Aborting".format(args.f)
                     )
-                    print("ERROR: {0}".format(str(exc)))
-                    cfg = {}
-        elif opt == "-m" and sys.platform == "darwin":
-            macAppBundleName = arg
-        elif opt == "-n" and sys.platform == "darwin":
-            macAppBundlePath = arg
-        elif opt == "-p" and sys.platform == "darwin":
-            macPythonExe = arg
-        elif opt == "--no-apis":
-            installApis = False
-        elif opt == "--clean-desktop":
-            doCleanDesktopLinks = True
-        elif opt == "--yes":
-            yes2All = True
-        elif opt == "--with-tools":
-            withPyqt6Tools = True
-        elif opt == "--no-info":
-            createInstallInfoFile = False
-        elif opt in ["-v", "--verbose"]:
-            verbose = True
+                    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)

eric ide

mercurial