--- 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)