--- a/scripts/install.py Sat Oct 03 11:14:23 2020 +0200 +++ b/scripts/install.py Sun Nov 01 11:15:18 2020 +0100 @@ -21,6 +21,9 @@ import time import io import json +import shlex +import datetime +import getpass # Define the globals. progName = None @@ -37,6 +40,7 @@ forceCleanDesktopLinks = False doCompile = True yes2All = False +ignorePyqt5Tools = False cfg = {} progLanguages = ["Python", "Ruby", "QSS"] sourceDir = "eric" @@ -52,6 +56,10 @@ macAppBundlePath = defaultMacAppBundlePath macPythonExe = defaultMacPythonExe +installInfoName = "eric6install.json" +installInfo = {} +installCwd = "" + # Define blacklisted versions of the prerequisites BlackLists = { "sip": [], @@ -117,11 +125,11 @@ .format(progName)) elif sys.platform.startswith(("win", "cygwin")): print(" {0} [-chxz] [-a dir] [-b dir] [-d dir] [-f file]" - " [--clean-desktop] [--no-apis] [--yes]" + " [--clean-desktop] [--no-apis] [--no-tools] [--yes]" .format(progName)) else: print(" {0} [-chxz] [-a dir] [-b dir] [-d dir] [-f file] [-i dir]" - " [--no-apis] [--yes]" + " [--no-apis] [--no-tools] [--yes]" .format(progName)) print("where:") print(" -h, --help display this help message") @@ -150,7 +158,9 @@ print(" (default: {0})".format(macPythonExe)) print(" -c don't cleanup old installation first") if sys.platform.startswith(("win", "cygwin")): - (" --clean-desktop delete desktop links before installation") + print(" --clean-desktop delete desktop links before installation") + if sys.platform != "darwin": + print(" --no-tools don't ask for installation of pyqt5-tools") print(" -x don't perform dependency checks (use on your own" " risk)") print(" -z don't compile the installed python files") @@ -235,9 +245,8 @@ @param name the name of the file. @param text the contents to copy to the file. """ - f = open(name, "w") - f.write(text) - f.close() + with open(name, "w") as f: + f.write(text) def copyDesktopFile(src, dst): @@ -249,17 +258,15 @@ """ global cfg, platBinDir - f = open(src, "r", encoding="utf-8") - text = f.read() - f.close() + with open(src, "r", encoding="utf-8") as f: + text = f.read() text = text.replace("@BINDIR@", platBinDir) text = text.replace("@MARKER@", "") text = text.replace("@PY_MARKER@", "") - f = open(dst, "w", encoding="utf-8") - f.write(text) - f.close() + with open(dst, "w", encoding="utf-8") as f: + f.write(text) os.chmod(dst, 0o644) @@ -279,9 +286,8 @@ else: Version = "Unknown" - f = open(src, "r", encoding="utf-8") - text = f.read() - f.close() + with open(src, "r", encoding="utf-8") as f: + text = f.read() text = ( text.replace("@MARKER@", "") @@ -289,9 +295,8 @@ .replace("@DATE@", time.strftime("%Y-%m-%d")) ) - f = open(dst, "w", encoding="utf-8") - f.write(text) - f.close() + with open(dst, "w", encoding="utf-8") as f: + f.write(text) os.chmod(dst, 0o644) @@ -424,16 +429,15 @@ if not os.path.exists(fname): if not os.path.exists(pdir): os.mkdir(pdir, 0o755) - f = open(fname, "w") - f.write( - '''# -*- coding: utf-8 -*- + with open(fname, "w") as f: + f.write( + '''# -*- coding: utf-8 -*- """ Package containing the global plugins. """ ''' - ) - f.close() + ) os.chmod(fname, 0o644) @@ -717,11 +721,10 @@ for name in ["eric6_compare", "eric6_configure", "eric6_diff", "eric6_editor", "eric6_hexeditor", "eric6_iconeditor", "eric6_plugininstall", "eric6_pluginrepository", - "eric6_pluginuninstall", "eric6_qregexp", - "eric6_qregularexpression", "eric6_re", "eric6_snap", - "eric6_sqlbrowser", "eric6_tray", "eric6_trpreviewer", - "eric6_uipreviewer", "eric6_unittest", "eric6_browser", - "eric6_shell", "eric6"]: + "eric6_pluginuninstall", "eric6_qregularexpression", + "eric6_re", "eric6_snap", "eric6_sqlbrowser", "eric6_tray", + "eric6_trpreviewer", "eric6_uipreviewer", "eric6_unittest", + "eric6_browser", "eric6_shell", "eric6"]: wnames.append(createPyWrapper(cfg['ericDir'], name, scriptsDir)) # set install prefix, if not None @@ -1240,6 +1243,13 @@ "MicroPython", "*.api"))): apis.append(os.path.basename(apiName)) + if sys.platform == "darwin": + macConfig = ( + """ 'macAppBundlePath': r'{0}',\n""" + """ 'macAppBundleName': r'{1}',\n""" + ).format(macAppBundlePath, macAppBundleName) + else: + macConfig = "" config = ( """# -*- coding: utf-8 -*-\n""" """#\n""" @@ -1264,8 +1274,7 @@ """ 'mdir': r'{13}',\n""" """ 'apidir': r'{14}',\n""" """ 'apis': {15},\n""" - """ 'macAppBundlePath': r'{16}',\n""" - """ 'macAppBundleName': r'{17}',\n""" + """{16}""" """}}\n""" """\n""" """def getConfig(name):\n""" @@ -1294,11 +1303,42 @@ cfg['ericCodeTemplatesDir'], cfg['ericOthersDir'], cfg['bindir'], cfg['mdir'], cfg['apidir'], sorted(apis), - macAppBundlePath, macAppBundleName, + macConfig, ) copyToFile(configName, config) +def createInstallInfo(): + """ + Record information about the way eric6 was installed. + """ + global installInfo, installCwd, cfg + + installDateTime = datetime.datetime.now(tz=None) + try: + installInfo["sudo"] = os.getuid() == 0 + except AttributeError: + installInfo["sudo"] = False + installInfo["user"] = getpass.getuser() + installInfo["exe"] = sys.executable + installInfo["argv"] = " ".join(shlex.quote(a) for a in sys.argv[:]) + installInfo["install_cwd"] = installCwd + installInfo["eric"] = cfg["ericDir"] + installInfo["virtualenv"] = installInfo["eric"].startswith( + os.path.expanduser("~")) + installInfo["installed"] = True + installInfo["installed_on"] = installDateTime.strftime( + "%Y-%m-%d %H:%M:%S") + installInfo["guessed"] = False + installInfo["edited"] = False + installInfo["pip"] = False + installInfo["remarks"] = "" + installInfo["install_cwd_edited"] = False + installInfo["exe_edited"] = False + installInfo["argv_edited"] = False + installInfo["eric_edited"] = False + + def pipInstall(packageName, message): """ Install the given package via pip. @@ -1376,10 +1416,15 @@ """ Perform some dependency checks. """ + try: + isSudo = os.getuid() == 0 + except AttributeError: + isSudo = False + print('Checking dependencies') # update pip first even if we don't need to install anything - if isPipOutdated(): + if not isSudo and isPipOutdated(): updatePip() print("\n") @@ -1399,7 +1444,7 @@ try: from PyQt5.QtCore import qVersion except ImportError as msg: - installed = pipInstall( + installed = not isSudo and pipInstall( "PyQt5", "'PyQt5' could not be detected.\nError: {0}".format(msg) ) @@ -1432,25 +1477,31 @@ from PyQt5.QtCore import PYQT_VERSION if PYQT_VERSION >= 0x050c00: # PyQt 5.12 separated QtWebEngine into a separate wheel - installed = pipInstall( - "PyQtWebEngine", - "Optional 'PyQtWebEngine' could not be detected.\nError: {0}" - .format(msg) - ) + if isSudo: + print("Optional 'PyQtWebEngine' could not be detected.") + else: + pipInstall( + "PyQtWebEngine", + "Optional 'PyQtWebEngine' could not be detected.\n" + "Error: {0}".format(msg) + ) try: from PyQt5 import QtChart # __IGNORE_WARNING__ except ImportError as msg: - installed = pipInstall( - "PyQtChart", - "Optional 'PyQtChart' could not be detected.\nError: {0}" - .format(msg) - ) + if isSudo: + print("Optional 'PyQtChart' could not be detected.") + else: + pipInstall( + "PyQtChart", + "Optional 'PyQtChart' could not be detected.\n" + "Error: {0}".format(msg) + ) try: from PyQt5 import Qsci # __IGNORE_WARNING__ except ImportError as msg: - installed = pipInstall( + installed = not isSudo and pipInstall( "QScintilla", "'QScintilla' could not be detected.\nError: {0}".format(msg) ) @@ -1479,6 +1530,10 @@ # available (e.g. for 32-Bit Windows) (("PyQt5.QtWebEngineWidgets", ), sys.maxsize <= 2**32), ] + optionalModulesList = {} + if sys.platform != "darwin" and not ignorePyqt5Tools: + optionalModulesList["pyqt5-tools"] = "pyqt5_tools" + # check mandatory modules modulesOK = True for impModule in impModulesList: @@ -1512,6 +1567,21 @@ .format(" or ".join(altModules))) if not altModulesOK: exit(1) + # check optional modules + for optPackage in optionalModulesList: + try: + __import__(optionalModulesList[optPackage]) + print("Found", optPackage) + except ImportError as msg: + if isSudo: + print("Optional '{0}' could not be detected." + .format(optPackage)) + else: + pipInstall( + optPackage, + "Optional '{0}' could not be detected.\n" + "Error: {1}".format(optPackage, msg) + ) # determine the platform dependent black list if sys.platform.startswith(("win", "cygwin")): @@ -1525,8 +1595,8 @@ qtMajor = int(qVersion().split('.')[0]) qtMinor = int(qVersion().split('.')[1]) print("Qt Version: {0}".format(qVersion().strip())) - if qtMajor == 5 and qtMinor < 9: - print('Sorry, you must have Qt version 5.9.0 or better.') + if qtMajor == 5 and qtMinor < 12: + print('Sorry, you must have Qt version 5.12.0 or better.') exit(2) # check version of sip @@ -1576,8 +1646,8 @@ major = int(major) minor = int(minor) pat = int(pat) - if major == 5 and minor < 9: - print('Sorry, you must have PyQt 5.9.0 or better or' + if major == 5 and minor < 12: + print('Sorry, you must have PyQt 5.12.0 or better or' ' a recent snapshot release.') exit(4) # check for blacklisted versions @@ -1600,8 +1670,12 @@ major = int(major) minor = int(minor) pat = int(pat) - if major < 2 or (major == 2 and minor < 9): - print('Sorry, you must have QScintilla 2.9.0 or higher or' + if ( + major < 2 or + (major == 2 and minor < 11) or + (major == 2 and minor == 11 and pat < 1) + ): + print('Sorry, you must have QScintilla 2.11.1 or higher or' ' a recent snapshot release.') exit(5) # check for blacklisted versions @@ -1673,9 +1747,8 @@ hgOut = hgOut.strip() if hgOut.endswith("+"): hgOut = hgOut[:-1] - f = open(fileName + ".orig", "r", encoding="utf-8") - text = f.read() - f.close() + with open(fileName + ".orig", "r", encoding="utf-8") as f: + text = f.read() text = ( text.replace("@@REVISION@@", hgOut) .replace("@@VERSION@@", "rev_" + hgOut) @@ -1797,7 +1870,8 @@ global progName, modDir, doCleanup, doCompile, distDir, cfg, apisDir global sourceDir, eric6SourceDir, configName global macAppBundlePath, macAppBundleName, macPythonExe - global installApis, doCleanDesktopLinks, yes2All + global installApis, doCleanDesktopLinks, yes2All, installCwd + global ignorePyqt5Tools if sys.version_info < (3, 5, 0) or sys.version_info > (3, 99, 99): print('Sorry, eric6 requires at least Python 3.5 for running.') @@ -1805,6 +1879,8 @@ progName = os.path.basename(argv[0]) + installCwd = os.getcwd() + if os.path.dirname(argv[0]): os.chdir(os.path.dirname(argv[0])) @@ -1814,7 +1890,7 @@ if sys.platform.startswith(("win", "cygwin")): optlist, args = getopt.getopt( argv[1:], "chxza:b:d:f:", - ["help", "no-apis", "yes"]) + ["help", "no-apis", "no-tools", "yes"]) elif sys.platform == "darwin": optlist, args = getopt.getopt( argv[1:], "chxza:b:d:f:i:m:n:p:", @@ -1822,7 +1898,7 @@ else: optlist, args = getopt.getopt( argv[1:], "chxza:b:d:f:i:", - ["help", "no-apis", "yes"]) + ["help", "no-apis", "no-tools", "yes"]) except getopt.GetoptError as err: print(err) usage() @@ -1870,6 +1946,8 @@ doCleanDesktopLinks = True elif opt == "--yes": yes2All = True + elif opt == "--no-tools": + ignorePyqt5Tools = True infoName = "" installFromSource = not os.path.isdir(sourceDir) @@ -1925,6 +2003,8 @@ print("\nCreating configuration file ...") createConfig() + createInstallInfo() + # Compile .ui files print("\nCompiling user interface files ...") # step 1: remove old Ui_*.py files @@ -1959,6 +2039,10 @@ print("\nInstalling eric6 ...") res = installEric() + with open(os.path.join(cfg["ericDir"], + installInfoName), "w") as installInfoFile: + json.dump(installInfo, installInfoFile, indent=2) + # do some cleanup try: if installFromSource: