scripts/install.py

branch
eric7-maintenance
changeset 9264
18a7312cfdb3
parent 9192
a763d57e23bc
parent 9261
66cc5e304450
child 9305
3b7ef53c34c7
equal deleted inserted replaced
9241:d23e9854aea4 9264:18a7312cfdb3
20 import os 20 import os
21 import py_compile 21 import py_compile
22 import re 22 import re
23 import shlex 23 import shlex
24 import shutil 24 import shutil
25 import subprocess # secok 25 import subprocess # secok
26 import time 26 import time
27 import sys 27 import sys
28 28
29 # Define the globals. 29 # Define the globals.
30 progName = None 30 progName = None
44 ignorePyqt6Tools = False 44 ignorePyqt6Tools = False
45 verbose = False 45 verbose = False
46 cfg = {} 46 cfg = {}
47 progLanguages = ["Python", "Ruby", "QSS"] 47 progLanguages = ["Python", "Ruby", "QSS"]
48 sourceDir = "eric" 48 sourceDir = "eric"
49 eric7SourceDir = os.path.join(sourceDir, "eric7") 49 eric7SourceDir = ""
50 configName = 'eric7config.py' 50 configName = "eric7config.py"
51 defaultMacAppBundleName = "eric7.app" 51 defaultMacAppBundleName = "eric7.app"
52 defaultMacAppBundlePath = "/Applications" 52 defaultMacAppBundlePath = "/Applications"
53 defaultMacPythonExe = "{0}/Resources/Python.app/Contents/MacOS/Python".format( 53 defaultMacPythonExe = "{0}/Resources/Python.app/Contents/MacOS/Python".format(
54 sys.exec_prefix) 54 sys.exec_prefix
55 )
55 if not os.path.exists(defaultMacPythonExe): 56 if not os.path.exists(defaultMacPythonExe):
56 defaultMacPythonExe = "" 57 defaultMacPythonExe = ""
57 macAppBundleName = defaultMacAppBundleName 58 macAppBundleName = defaultMacAppBundleName
58 macAppBundlePath = defaultMacAppBundlePath 59 macAppBundlePath = defaultMacAppBundlePath
59 macPythonExe = defaultMacPythonExe 60 macPythonExe = defaultMacPythonExe
73 "windows": { 74 "windows": {
74 "sip": [], 75 "sip": [],
75 "PyQt6": [], 76 "PyQt6": [],
76 "QScintilla2": [], 77 "QScintilla2": [],
77 }, 78 },
78
79 "linux": { 79 "linux": {
80 "sip": [], 80 "sip": [],
81 "PyQt6": [], 81 "PyQt6": [],
82 "QScintilla2": [], 82 "QScintilla2": [],
83 }, 83 },
84
85 "mac": { 84 "mac": {
86 "sip": [], 85 "sip": [],
87 "PyQt6": [], 86 "PyQt6": [],
88 "QScintilla2": [], 87 "QScintilla2": [],
89 }, 88 },
91 90
92 91
93 def exit(rcode=0): 92 def exit(rcode=0):
94 """ 93 """
95 Exit the install script. 94 Exit the install script.
96 95
97 @param rcode result code to report back (integer) 96 @param rcode result code to report back (integer)
98 """ 97 """
99 global currDir 98 global currDir
100 99
101 print() 100 print()
102 101
103 if sys.platform.startswith(("win", "cygwin")): 102 if sys.platform.startswith(("win", "cygwin")):
104 with contextlib.suppress(): 103 with contextlib.suppress():
105 input("Press enter to continue...") # secok 104 input("Press enter to continue...") # secok
106 105
107 os.chdir(currDir) 106 os.chdir(currDir)
108 107
109 sys.exit(rcode) 108 sys.exit(rcode)
110 109
111 110
112 def usage(rcode=2): 111 def usage(rcode=2):
113 """ 112 """
119 global macAppBundleName, macAppBundlePath, macPythonExe 118 global macAppBundleName, macAppBundlePath, macPythonExe
120 119
121 print() 120 print()
122 print("Usage:") 121 print("Usage:")
123 if sys.platform == "darwin": 122 if sys.platform == "darwin":
124 print(" {0} [-chvxz] [-a dir] [-b dir] [-d dir] [-f file] [-i dir]" 123 print(
125 " [-m name] [-n path] [-p python] [--help] [--no-apis]" 124 " {0} [-chvxz] [-a dir] [-b dir] [-d dir] [-f file] [-i dir]"
126 " [--no-info] [--no-tools] [--verbose] [--yes]" 125 " [-m name] [-n path] [-p python] [--help] [--no-apis]"
127 .format(progName)) 126 " [--no-info] [--no-tools] [--verbose] [--yes]".format(progName)
127 )
128 elif sys.platform.startswith(("win", "cygwin")): 128 elif sys.platform.startswith(("win", "cygwin")):
129 print(" {0} [-chvxz] [-a dir] [-b dir] [-d dir] [-f file]" 129 print(
130 " [--clean-desktop] [--help] [--no-apis] [--no-info]" 130 " {0} [-chvxz] [-a dir] [-b dir] [-d dir] [-f file]"
131 " [--no-tools] [--verbose] [--yes]" 131 " [--clean-desktop] [--help] [--no-apis] [--no-info]"
132 .format(progName)) 132 " [--no-tools] [--verbose] [--yes]".format(progName)
133 )
133 else: 134 else:
134 print(" {0} [-chvxz] [-a dir] [-b dir] [-d dir] [-f file] [-i dir]" 135 print(
135 " [--help] [--no-apis] [--no-info] [--no-tools] [--verbose]" 136 " {0} [-chvxz] [-a dir] [-b dir] [-d dir] [-f file] [-i dir]"
136 " [--yes]" 137 " [--help] [--no-apis] [--no-info] [--no-tools] [--verbose]"
137 .format(progName)) 138 " [--yes]".format(progName)
139 )
138 print("where:") 140 print("where:")
139 print(" -h, --help display this help message") 141 print(" -h, --help display this help message")
140 print(" -a dir where the API files will be installed") 142 print(" -a dir where the API files will be installed")
141 if apisDir: 143 if apisDir:
142 print(" (default: {0})".format(apisDir)) 144 print(" (default: {0})".format(apisDir))
145 print(" --no-apis don't install API files") 147 print(" --no-apis don't install API files")
146 print(" -b dir where the binaries will be installed") 148 print(" -b dir where the binaries will be installed")
147 print(" (default: {0})".format(platBinDir)) 149 print(" (default: {0})".format(platBinDir))
148 print(" -d dir where eric python files will be installed") 150 print(" -d dir where eric python files will be installed")
149 print(" (default: {0})".format(modDir)) 151 print(" (default: {0})".format(modDir))
150 print(" -f file configuration file naming the various installation" 152 print(" -f file configuration file naming the various installation" " paths")
151 " paths")
152 if not sys.platform.startswith(("win", "cygwin")): 153 if not sys.platform.startswith(("win", "cygwin")):
153 print(" -i dir temporary install prefix") 154 print(" -i dir temporary install prefix")
154 print(" (default: {0})".format(distDir)) 155 print(" (default: {0})".format(distDir))
155 if sys.platform == "darwin": 156 if sys.platform == "darwin":
156 print(" -m name name of the Mac app bundle") 157 print(" -m name name of the Mac app bundle")
164 if sys.platform.startswith(("win", "cygwin")): 165 if sys.platform.startswith(("win", "cygwin")):
165 print(" --clean-desktop delete desktop links before installation") 166 print(" --clean-desktop delete desktop links before installation")
166 print(" --no-info don't create the install info file") 167 print(" --no-info don't create the install info file")
167 print(" --no-tools don't install qt6-applications") 168 print(" --no-tools don't install qt6-applications")
168 print(" -v, --verbose print some more information") 169 print(" -v, --verbose print some more information")
169 print(" -x don't perform dependency checks (use on your own" 170 print(" -x don't perform dependency checks (use on your own" " risk)")
170 " risk)")
171 print(" -z don't compile the installed python files") 171 print(" -z don't compile the installed python files")
172 print(" --yes answer 'yes' to all questions") 172 print(" --yes answer 'yes' to all questions")
173 print() 173 print()
174 print("The file given to the -f option must be valid Python code" 174 print("The file given to the -f option must be valid Python code" " defining a")
175 " defining a") 175 print(
176 print("dictionary called 'cfg' with the keys 'ericDir', 'ericPixDir'," 176 "dictionary called 'cfg' with the keys 'ericDir', 'ericPixDir',"
177 " 'ericIconDir',") 177 " 'ericIconDir',"
178 )
178 print("'ericDTDDir', 'ericCSSDir', 'ericStylesDir', 'ericThemesDir',") 179 print("'ericDTDDir', 'ericCSSDir', 'ericStylesDir', 'ericThemesDir',")
179 print(" 'ericDocDir', ericExamplesDir',") 180 print(" 'ericDocDir', ericExamplesDir',")
180 print("'ericTranslationsDir', 'ericTemplatesDir', 'ericCodeTemplatesDir',") 181 print("'ericTranslationsDir', 'ericTemplatesDir', 'ericCodeTemplatesDir',")
181 print("'ericOthersDir','bindir', 'mdir' and 'apidir.") 182 print("'ericOthersDir','bindir', 'mdir' and 'apidir.")
182 print("These define the directories for the installation of the various" 183 print(
183 " parts of eric.") 184 "These define the directories for the installation of the various"
185 " parts of eric."
186 )
184 187
185 exit(rcode) 188 exit(rcode)
186 189
187 190
188 def initGlobals(): 191 def initGlobals():
189 """ 192 """
190 Module function to set the values of globals that need more than a 193 Module function to set the values of globals that need more than a
191 simple assignment. 194 simple assignment.
192 """ 195 """
193 global platBinDir, modDir, pyModDir, apisDir, platBinDirOld 196 global platBinDir, modDir, pyModDir, apisDir, platBinDirOld
194 197
195 import sysconfig 198 import sysconfig
196 199
197 if sys.platform.startswith(("win", "cygwin")): 200 if sys.platform.startswith(("win", "cygwin")):
198 platBinDir = sys.exec_prefix 201 platBinDir = sys.exec_prefix
199 if platBinDir.endswith("\\"): 202 if platBinDir.endswith("\\"):
200 platBinDir = platBinDir[:-1] 203 platBinDir = platBinDir[:-1]
201 platBinDirOld = platBinDir 204 platBinDirOld = platBinDir
208 # install the eric scripts along the python executable 211 # install the eric scripts along the python executable
209 platBinDir = pyBinDir 212 platBinDir = pyBinDir
210 else: 213 else:
211 # install them in the user's bin directory 214 # install them in the user's bin directory
212 platBinDir = os.path.expanduser("~/bin") 215 platBinDir = os.path.expanduser("~/bin")
213 if ( 216 if platBinDir != "/usr/local/bin" and os.access("/usr/local/bin", os.W_OK):
214 platBinDir != "/usr/local/bin" and
215 os.access("/usr/local/bin", os.W_OK)
216 ):
217 platBinDirOld = "/usr/local/bin" 217 platBinDirOld = "/usr/local/bin"
218 218
219 modDir = sysconfig.get_path('platlib') 219 modDir = sysconfig.get_path("platlib")
220 pyModDir = modDir 220 pyModDir = modDir
221 221
222 pyqtDataDir = os.path.join(modDir, "PyQt6") 222 pyqtDataDir = os.path.join(modDir, "PyQt6")
223 if os.path.exists(os.path.join(pyqtDataDir, "qsci")): 223 if os.path.exists(os.path.join(pyqtDataDir, "qsci")):
224 # it's the installer 224 # it's the installer
225 qtDataDir = pyqtDataDir 225 qtDataDir = pyqtDataDir
226 elif os.path.exists(os.path.join(pyqtDataDir, "Qt6", "qsci")): 226 elif os.path.exists(os.path.join(pyqtDataDir, "Qt6", "qsci")):
228 qtDataDir = os.path.join(pyqtDataDir, "Qt6") 228 qtDataDir = os.path.join(pyqtDataDir, "Qt6")
229 else: 229 else:
230 # determine dynamically 230 # determine dynamically
231 try: 231 try:
232 from PyQt6.QtCore import QLibraryInfo 232 from PyQt6.QtCore import QLibraryInfo
233
233 qtDataDir = QLibraryInfo.path(QLibraryInfo.LibraryPath.DataPath) 234 qtDataDir = QLibraryInfo.path(QLibraryInfo.LibraryPath.DataPath)
234 except ImportError: 235 except ImportError:
235 qtDataDir = None 236 qtDataDir = None
236 apisDir = os.path.join(qtDataDir, "qsci", "api") if qtDataDir else None 237 apisDir = os.path.join(qtDataDir, "qsci", "api") if qtDataDir else None
237 238
248 249
249 250
250 def copyDesktopFile(src, dst): 251 def copyDesktopFile(src, dst):
251 """ 252 """
252 Modify a desktop file and write it to its destination. 253 Modify a desktop file and write it to its destination.
253 254
254 @param src source file name (string) 255 @param src source file name (string)
255 @param dst destination file name (string) 256 @param dst destination file name (string)
256 """ 257 """
257 global cfg, platBinDir 258 global cfg, platBinDir
258 259
259 with open(src, "r", encoding="utf-8") as f: 260 with open(src, "r", encoding="utf-8") as f:
260 text = f.read() 261 text = f.read()
261 262
262 text = text.replace("@BINDIR@", platBinDir) 263 text = text.replace("@BINDIR@", platBinDir)
263 text = text.replace("@MARKER@", "") 264 text = text.replace("@MARKER@", "")
264 text = text.replace("@PY_MARKER@", "") 265 text = text.replace("@PY_MARKER@", "")
265 266
266 with open(dst, "w", encoding="utf-8") as f: 267 with open(dst, "w", encoding="utf-8") as f:
267 f.write(text) 268 f.write(text)
268 os.chmod(dst, 0o644) 269 os.chmod(dst, 0o644)
269 270
270 271
271 def copyAppStreamFile(src, dst): 272 def copyAppStreamFile(src, dst):
272 """ 273 """
273 Modify an appstream file and write it to its destination. 274 Modify an appstream file and write it to its destination.
274 275
275 @param src source file name (string) 276 @param src source file name (string)
276 @param dst destination file name (string) 277 @param dst destination file name (string)
277 """ 278 """
278 if os.path.exists(os.path.join("eric", "eric7", "UI", "Info.py")): 279 if os.path.exists(os.path.join("eric", "src", "eric7", "UI", "Info.py")):
279 # Installing from archive 280 # Installing from installer archive
280 from eric.eric7.UI.Info import Version 281 from eric.src.eric7.UI.Info import Version
281 elif os.path.exists(os.path.join("eric7", "UI", "Info.py")): 282 elif os.path.exists(os.path.join("src", "eric7", "UI", "Info.py")):
282 # Installing from source tree 283 # Installing from source tree
283 from eric7.UI.Info import Version 284 from src.eric7.UI.Info import Version
284 else: 285 else:
285 Version = "Unknown" 286 Version = "Unknown"
286 287
287 with open(src, "r", encoding="utf-8") as f: 288 with open(src, "r", encoding="utf-8") as f:
288 text = f.read() 289 text = f.read()
289 290
290 text = ( 291 text = (
291 text.replace("@MARKER@", "") 292 text.replace("@MARKER@", "")
292 .replace("@VERSION@", Version.split(None, 1)[0]) 293 .replace("@VERSION@", Version.split(None, 1)[0])
293 .replace("@DATE@", time.strftime("%Y-%m-%d")) 294 .replace("@DATE@", time.strftime("%Y-%m-%d"))
294 ) 295 )
295 296
296 with open(dst, "w", encoding="utf-8") as f: 297 with open(dst, "w", encoding="utf-8") as f:
297 f.write(text) 298 f.write(text)
298 os.chmod(dst, 0o644) 299 os.chmod(dst, 0o644)
299 300
300 301
301 def wrapperNames(dname, wfile): 302 def wrapperNames(dname, wfile):
302 """ 303 """
303 Create the platform specific names for the wrapper script. 304 Create the platform specific names for the wrapper script.
304 305
305 @param dname name of the directory to place the wrapper into 306 @param dname name of the directory to place the wrapper into
306 @param wfile basename (without extension) of the wrapper script 307 @param wfile basename (without extension) of the wrapper script
307 @return the names of the wrapper scripts 308 @return the names of the wrapper scripts
308 """ 309 """
309 wnames = ( 310 wnames = (
310 (dname + "\\" + wfile + ".cmd", dname + "\\" + wfile + ".bat") 311 (dname + "\\" + wfile + ".cmd", dname + "\\" + wfile + ".bat")
311 if sys.platform.startswith(("win", "cygwin")) else 312 if sys.platform.startswith(("win", "cygwin"))
312 (dname + "/" + wfile, ) 313 else (dname + "/" + wfile,)
313 ) 314 )
314 315
315 return wnames 316 return wnames
316 317
317 318
330 # all kinds of Windows systems 331 # all kinds of Windows systems
331 if sys.platform.startswith(("win", "cygwin")): 332 if sys.platform.startswith(("win", "cygwin")):
332 wname = wfile + ".cmd" 333 wname = wfile + ".cmd"
333 if isGuiScript: 334 if isGuiScript:
334 wrapper = ( 335 wrapper = (
335 '''@echo off\n''' 336 """@echo off\n"""
336 '''start "" "{2}\\pythonw.exe"''' 337 '''start "" "{2}\\pythonw.exe"'''
337 ''' "{0}\\{1}.pyw"''' 338 ''' "{0}\\{1}.pyw"'''
338 ''' %1 %2 %3 %4 %5 %6 %7 %8 %9\n'''.format( 339 """ %1 %2 %3 %4 %5 %6 %7 %8 %9\n""".format(
339 pydir, wfile, os.path.dirname(sys.executable)) 340 pydir, wfile, os.path.dirname(sys.executable)
341 )
340 ) 342 )
341 else: 343 else:
342 wrapper = ( 344 wrapper = (
343 '''@"{0}" "{1}\\{2}.py"''' 345 '''@"{0}" "{1}\\{2}.py"'''
344 ''' %1 %2 %3 %4 %5 %6 %7 %8 %9\n'''.format( 346 """ %1 %2 %3 %4 %5 %6 %7 %8 %9\n""".format(sys.executable, pydir, wfile)
345 sys.executable, pydir, wfile)
346 ) 347 )
347 348
348 # Mac OS X 349 # Mac OS X
349 elif sys.platform == "darwin": 350 elif sys.platform == "darwin":
350 major = sys.version_info.major 351 major = sys.version_info.major
351 pyexec = "{0}/bin/pythonw{1}".format(sys.exec_prefix, major) 352 pyexec = "{0}/bin/pythonw{1}".format(sys.exec_prefix, major)
352 if not os.path.exists(pyexec): 353 if not os.path.exists(pyexec):
353 pyexec = "{0}/bin/python{1}".format(sys.exec_prefix, major) 354 pyexec = "{0}/bin/python{1}".format(sys.exec_prefix, major)
354 wname = wfile 355 wname = wfile
355 wrapper = ('''#!/bin/sh\n''' 356 wrapper = (
356 '''\n''' 357 """#!/bin/sh\n"""
357 '''exec "{0}" "{1}/{2}.py" "$@"\n''' 358 """\n"""
358 .format(pyexec, pydir, wfile)) 359 """exec "{0}" "{1}/{2}.py" "$@"\n""".format(pyexec, pydir, wfile)
360 )
359 361
360 # *nix systems 362 # *nix systems
361 else: 363 else:
362 wname = wfile 364 wname = wfile
363 wrapper = ('''#!/bin/sh\n''' 365 wrapper = (
364 '''\n''' 366 """#!/bin/sh\n"""
365 '''exec "{0}" "{1}/{2}.py" "$@"\n''' 367 """\n"""
366 .format(sys.executable, pydir, wfile)) 368 """exec "{0}" "{1}/{2}.py" "$@"\n""".format(sys.executable, pydir, wfile)
367 369 )
370
368 wname = os.path.join(saveDir, wname) 371 wname = os.path.join(saveDir, wname)
369 copyToFile(wname, wrapper) 372 copyToFile(wname, wrapper)
370 os.chmod(wname, 0o755) # secok 373 os.chmod(wname, 0o755) # secok
371 374
372 return wname 375 return wname
373 376
374 377
375 def copyTree(src, dst, filters, excludeDirs=None, excludePatterns=None): 378 def copyTree(src, dst, filters, excludeDirs=None, excludePatterns=None):
376 """ 379 """
377 Copy Python, translation, documentation, wizards configuration, 380 Copy Python, translation, documentation, wizards configuration,
378 designer template files and DTDs of a directory tree. 381 designer template files and DTDs of a directory tree.
379 382
380 @param src name of the source directory 383 @param src name of the source directory
381 @param dst name of the destination directory 384 @param dst name of the destination directory
382 @param filters list of filter pattern determining the files to be copied 385 @param filters list of filter pattern determining the files to be copied
383 @param excludeDirs list of (sub)directories to exclude from copying 386 @param excludeDirs list of (sub)directories to exclude from copying
384 @param excludePatterns list of filter pattern determining the files to 387 @param excludePatterns list of filter pattern determining the files to
391 try: 394 try:
392 names = os.listdir(src) 395 names = os.listdir(src)
393 except OSError: 396 except OSError:
394 # ignore missing directories (most probably the i18n directory) 397 # ignore missing directories (most probably the i18n directory)
395 return 398 return
396 399
397 for name in names: 400 for name in names:
398 skipIt = False 401 skipIt = False
399 for excludePattern in excludePatterns: 402 for excludePattern in excludePatterns:
400 if fnmatch.fnmatch(name, excludePattern): 403 if fnmatch.fnmatch(name, excludePattern):
401 skipIt = True 404 skipIt = True
410 shutil.copy2(srcname, dstname) 413 shutil.copy2(srcname, dstname)
411 os.chmod(dstname, 0o644) 414 os.chmod(dstname, 0o644)
412 break 415 break
413 else: 416 else:
414 if os.path.isdir(srcname) and srcname not in excludeDirs: 417 if os.path.isdir(srcname) and srcname not in excludeDirs:
415 copyTree(srcname, dstname, filters, 418 copyTree(srcname, dstname, filters, excludePatterns=excludePatterns)
416 excludePatterns=excludePatterns)
417 419
418 420
419 def createGlobalPluginsDir(): 421 def createGlobalPluginsDir():
420 """ 422 """
421 Create the global plugins directory, if it doesn't exist. 423 Create the global plugins directory, if it doesn't exist.
422 """ 424 """
423 global cfg, distDir 425 global cfg, distDir
424 426
425 pdir = os.path.join(cfg['mdir'], "eric7plugins") 427 pdir = os.path.join(cfg["mdir"], "eric7plugins")
426 fname = os.path.join(pdir, "__init__.py") 428 fname = os.path.join(pdir, "__init__.py")
427 if not os.path.exists(fname): 429 if not os.path.exists(fname):
428 if not os.path.exists(pdir): 430 if not os.path.exists(pdir):
429 os.mkdir(pdir, 0o755) 431 os.mkdir(pdir, 0o755)
430 with open(fname, "w") as f: 432 with open(fname, "w") as f:
441 443
442 def cleanupSource(dirName): 444 def cleanupSource(dirName):
443 """ 445 """
444 Cleanup the sources directory to get rid of leftover files 446 Cleanup the sources directory to get rid of leftover files
445 and directories. 447 and directories.
446 448
447 @param dirName name of the directory to prune (string) 449 @param dirName name of the directory to prune (string)
448 """ 450 """
449 # step 1: delete all Ui_*.py files without a corresponding 451 # step 1: delete all Ui_*.py files without a corresponding
450 # *.ui file 452 # *.ui file
451 dirListing = os.listdir(dirName) 453 dirListing = os.listdir(dirName)
452 for formName, sourceName in [ 454 for formName, sourceName in [
453 (f.replace('Ui_', "").replace(".py", ".ui"), f) 455 (f.replace("Ui_", "").replace(".py", ".ui"), f)
454 for f in dirListing if fnmatch.fnmatch(f, "Ui_*.py")]: 456 for f in dirListing
457 if fnmatch.fnmatch(f, "Ui_*.py")
458 ]:
455 if not os.path.exists(os.path.join(dirName, formName)): 459 if not os.path.exists(os.path.join(dirName, formName)):
456 os.remove(os.path.join(dirName, sourceName)) 460 os.remove(os.path.join(dirName, sourceName))
457 if os.path.exists(os.path.join(dirName, sourceName + "c")): 461 if os.path.exists(os.path.join(dirName, sourceName + "c")):
458 os.remove(os.path.join(dirName, sourceName + "c")) 462 os.remove(os.path.join(dirName, sourceName + "c"))
459 463
460 # step 2: delete the __pycache__ directory and all remaining *.pyc files 464 # step 2: delete the __pycache__ directory and all remaining *.pyc files
461 if os.path.exists(os.path.join(dirName, "__pycache__")): 465 if os.path.exists(os.path.join(dirName, "__pycache__")):
462 shutil.rmtree(os.path.join(dirName, "__pycache__")) 466 shutil.rmtree(os.path.join(dirName, "__pycache__"))
463 for name in [f for f in os.listdir(dirName) 467 for name in [f for f in os.listdir(dirName) if fnmatch.fnmatch(f, "*.pyc")]:
464 if fnmatch.fnmatch(f, "*.pyc")]:
465 os.remove(os.path.join(dirName, name)) 468 os.remove(os.path.join(dirName, name))
466 469
467 # step 3: delete *.orig files 470 # step 3: delete *.orig files
468 for name in [f for f in os.listdir(dirName) 471 for name in [f for f in os.listdir(dirName) if fnmatch.fnmatch(f, "*.orig")]:
469 if fnmatch.fnmatch(f, "*.orig")]:
470 os.remove(os.path.join(dirName, name)) 472 os.remove(os.path.join(dirName, name))
471 473
472 # step 4: descent into subdirectories and delete them if empty 474 # step 4: descent into subdirectories and delete them if empty
473 for name in os.listdir(dirName): 475 for name in os.listdir(dirName):
474 name = os.path.join(dirName, name) 476 name = os.path.join(dirName, name)
475 if os.path.isdir(name): 477 if os.path.isdir(name):
476 cleanupSource(name) 478 cleanupSource(name)
481 def cleanUp(): 483 def cleanUp():
482 """ 484 """
483 Uninstall the old eric files. 485 Uninstall the old eric files.
484 """ 486 """
485 global platBinDir, platBinDirOld 487 global platBinDir, platBinDirOld
486 488
487 try: 489 try:
488 from eric7config import getConfig 490 from eric7config import getConfig
489 except ImportError: 491 except ImportError:
490 # eric wasn't installed previously 492 # eric wasn't installed previously
491 return 493 return
492 except SyntaxError: 494 except SyntaxError:
493 # an incomplete or old config file was found 495 # an incomplete or old config file was found
494 return 496 return
495 497
496 global pyModDir, progLanguages 498 global pyModDir, progLanguages
497 499
498 # Remove the menu entry for Linux systems 500 # Remove the menu entry for Linux systems
499 if sys.platform.startswith("linux"): 501 if sys.platform.startswith("linux"):
500 cleanUpLinuxSpecifics() 502 cleanUpLinuxSpecifics()
501 # Remove the Desktop and Start Menu entries for Windows systems 503 # Remove the Desktop and Start Menu entries for Windows systems
502 elif sys.platform.startswith(("win", "cygwin")): 504 elif sys.platform.startswith(("win", "cygwin")):
503 cleanUpWindowsLinks() 505 cleanUpWindowsLinks()
504 506
505 # Remove the wrapper scripts 507 # Remove the wrapper scripts
506 rem_wnames = [ 508 rem_wnames = [
507 "eric7_api", "eric7_browser", "eric7_compare", "eric7_configure", 509 "eric7_api",
508 "eric7_diff", "eric7_doc", "eric7_editor", "eric7_hexeditor", 510 "eric7_browser",
509 "eric7_iconeditor", "eric7_plugininstall", "eric7_pluginrepository", 511 "eric7_compare",
510 "eric7_pluginuninstall", "eric7_qregularexpression", "eric7_re", 512 "eric7_configure",
511 "eric7_shell", "eric7_snap", "eric7_sqlbrowser", "eric7_testing", 513 "eric7_diff",
512 "eric7_tray", "eric7_trpreviewer", "eric7_uipreviewer", 514 "eric7_doc",
513 "eric7_virtualenv", "eric7", 515 "eric7_editor",
516 "eric7_hexeditor",
517 "eric7_iconeditor",
518 "eric7_plugininstall",
519 "eric7_pluginrepository",
520 "eric7_pluginuninstall",
521 "eric7_qregularexpression",
522 "eric7_re",
523 "eric7_shell",
524 "eric7_snap",
525 "eric7_sqlbrowser",
526 "eric7_testing",
527 "eric7_tray",
528 "eric7_trpreviewer",
529 "eric7_uipreviewer",
530 "eric7_virtualenv",
531 "eric7",
514 # obsolete scripts below 532 # obsolete scripts below
515 "eric7_unittest", 533 "eric7_unittest",
516 ] 534 ]
517 535
518 try: 536 try:
519 dirs = [platBinDir, getConfig('bindir')] 537 dirs = [platBinDir, getConfig("bindir")]
520 if platBinDirOld: 538 if platBinDirOld:
521 dirs.append(platBinDirOld) 539 dirs.append(platBinDirOld)
522 for rem_wname in rem_wnames: 540 for rem_wname in rem_wnames:
523 for d in dirs: 541 for d in dirs:
524 for rwname in wrapperNames(d, rem_wname): 542 for rwname in wrapperNames(d, rem_wname):
525 if os.path.exists(rwname): 543 if os.path.exists(rwname):
526 os.remove(rwname) 544 os.remove(rwname)
527 545
528 # Cleanup our config file(s) 546 # Cleanup our config file(s)
529 for name in ['eric7config.py', 'eric7config.pyc', 'eric7.pth']: 547 for name in ["eric7config.py", "eric7config.pyc", "eric7.pth"]:
530 e6cfile = os.path.join(pyModDir, name) 548 e6cfile = os.path.join(pyModDir, name)
531 if os.path.exists(e6cfile): 549 if os.path.exists(e6cfile):
532 os.remove(e6cfile) 550 os.remove(e6cfile)
533 e6cfile = os.path.join(pyModDir, "__pycache__", name) 551 e6cfile = os.path.join(pyModDir, "__pycache__", name)
534 path, ext = os.path.splitext(e6cfile) 552 path, ext = os.path.splitext(e6cfile)
535 for f in glob.glob("{0}.*{1}".format(path, ext)): 553 for f in glob.glob("{0}.*{1}".format(path, ext)):
536 os.remove(f) 554 os.remove(f)
537 555
538 # Cleanup the install directories 556 # Cleanup the install directories
539 for name in ['ericExamplesDir', 'ericDocDir', 'ericDTDDir', 557 for name in [
540 'ericCSSDir', 'ericIconDir', 'ericPixDir', 558 "ericExamplesDir",
541 'ericTemplatesDir', 'ericCodeTemplatesDir', 559 "ericDocDir",
542 'ericOthersDir', 'ericStylesDir', 'ericThemesDir', 560 "ericDTDDir",
543 'ericDir']: 561 "ericCSSDir",
562 "ericIconDir",
563 "ericPixDir",
564 "ericTemplatesDir",
565 "ericCodeTemplatesDir",
566 "ericOthersDir",
567 "ericStylesDir",
568 "ericThemesDir",
569 "ericDir",
570 ]:
544 with contextlib.suppress(AttributeError): 571 with contextlib.suppress(AttributeError):
545 if os.path.exists(getConfig(name)): 572 if os.path.exists(getConfig(name)):
546 shutil.rmtree(getConfig(name), True) 573 shutil.rmtree(getConfig(name), True)
547 574
548 # Cleanup translations 575 # Cleanup translations
549 for name in glob.glob( 576 for name in glob.glob(
550 os.path.join(getConfig('ericTranslationsDir'), 'eric7_*.qm')): 577 os.path.join(getConfig("ericTranslationsDir"), "eric7_*.qm")
578 ):
551 if os.path.exists(name): 579 if os.path.exists(name):
552 os.remove(name) 580 os.remove(name)
553 581
554 # Cleanup API files 582 # Cleanup API files
555 with contextlib.suppress(AttributeError): 583 with contextlib.suppress(AttributeError):
556 apidir = getConfig('apidir') 584 apidir = getConfig("apidir")
557 for progLanguage in progLanguages: 585 for progLanguage in progLanguages:
558 for name in getConfig('apis'): 586 for name in getConfig("apis"):
559 apiname = os.path.join(apidir, progLanguage.lower(), name) 587 apiname = os.path.join(apidir, progLanguage.lower(), name)
560 if os.path.exists(apiname): 588 if os.path.exists(apiname):
561 os.remove(apiname) 589 os.remove(apiname)
562 for apiname in glob.glob( 590 for apiname in glob.glob(
563 os.path.join(apidir, progLanguage.lower(), "*.bas")): 591 os.path.join(apidir, progLanguage.lower(), "*.bas")
592 ):
564 if os.path.basename(apiname) != "eric7.bas": 593 if os.path.basename(apiname) != "eric7.bas":
565 os.remove(apiname) 594 os.remove(apiname)
566 595
567 if sys.platform == "darwin": 596 if sys.platform == "darwin":
568 # delete the Mac app bundle 597 # delete the Mac app bundle
569 cleanUpMacAppBundle() 598 cleanUpMacAppBundle()
570 except OSError as msg: 599 except OSError as msg:
571 sys.stderr.write( 600 sys.stderr.write("Error: {0}\nTry install with admin rights.\n".format(msg))
572 'Error: {0}\nTry install with admin rights.\n'.format(msg))
573 exit(7) 601 exit(7)
574 602
575 603
576 def cleanUpLinuxSpecifics(): 604 def cleanUpLinuxSpecifics():
577 """ 605 """
582 "/usr/share/applications/eric7.desktop", 610 "/usr/share/applications/eric7.desktop",
583 "/usr/share/appdata/eric7.appdata.xml", 611 "/usr/share/appdata/eric7.appdata.xml",
584 "/usr/share/metainfo/eric7.appdata.xml", 612 "/usr/share/metainfo/eric7.appdata.xml",
585 "/usr/share/pixmaps/eric.png", 613 "/usr/share/pixmaps/eric.png",
586 "/usr/share/icons/eric.png", 614 "/usr/share/icons/eric.png",
587
588 "/usr/share/applications/eric7_browser.desktop", 615 "/usr/share/applications/eric7_browser.desktop",
589 "/usr/share/pixmaps/ericWeb.png", 616 "/usr/share/pixmaps/ericWeb.png",
590 "/usr/share/icons/ericWeb.png", 617 "/usr/share/icons/ericWeb.png",
591 ]: 618 ]:
592 if os.path.exists(name): 619 if os.path.exists(name):
597 "~/.local/share/applications/eric7.desktop", 624 "~/.local/share/applications/eric7.desktop",
598 "~/.local/share/appdata/eric7.appdata.xml", 625 "~/.local/share/appdata/eric7.appdata.xml",
599 "~/.local/share/metainfo/eric7.appdata.xml", 626 "~/.local/share/metainfo/eric7.appdata.xml",
600 "~/.local/share/pixmaps/eric.png", 627 "~/.local/share/pixmaps/eric.png",
601 "~/.local/share/icons/eric.png", 628 "~/.local/share/icons/eric.png",
602
603 "~/.local/share/applications/eric7_browser.desktop", 629 "~/.local/share/applications/eric7_browser.desktop",
604 "~/.local/share/pixmaps/ericWeb.png", 630 "~/.local/share/pixmaps/ericWeb.png",
605 "~/.local/share/icons/ericWeb.png", 631 "~/.local/share/icons/ericWeb.png",
606 ]: 632 ]:
607 path = os.path.expanduser(name) 633 path = os.path.expanduser(name)
612 def cleanUpMacAppBundle(): 638 def cleanUpMacAppBundle():
613 """ 639 """
614 Uninstall the macOS application bundle. 640 Uninstall the macOS application bundle.
615 """ 641 """
616 from eric7config import getConfig 642 from eric7config import getConfig
643
617 try: 644 try:
618 macAppBundlePath = getConfig("macAppBundlePath") 645 macAppBundlePath = getConfig("macAppBundlePath")
619 macAppBundleName = getConfig("macAppBundleName") 646 macAppBundleName = getConfig("macAppBundleName")
620 except AttributeError: 647 except AttributeError:
621 macAppBundlePath = defaultMacAppBundlePath 648 macAppBundlePath = defaultMacAppBundlePath
622 macAppBundleName = defaultMacAppBundleName 649 macAppBundleName = defaultMacAppBundleName
623 for bundlePath in [os.path.join(defaultMacAppBundlePath, 650 for bundlePath in [
624 macAppBundleName), 651 os.path.join(defaultMacAppBundlePath, macAppBundleName),
625 os.path.join(macAppBundlePath, 652 os.path.join(macAppBundlePath, macAppBundleName),
626 macAppBundleName), 653 ]:
627 ]:
628 if os.path.exists(bundlePath): 654 if os.path.exists(bundlePath):
629 shutil.rmtree(bundlePath) 655 shutil.rmtree(bundlePath)
630 656
631 657
632 def cleanUpWindowsLinks(): 658 def cleanUpWindowsLinks():
633 """ 659 """
634 Clean up the Desktop and Start Menu entries for Windows. 660 Clean up the Desktop and Start Menu entries for Windows.
635 """ 661 """
636 global doCleanDesktopLinks, forceCleanDesktopLinks 662 global doCleanDesktopLinks, forceCleanDesktopLinks
637 663
638 try: 664 try:
639 from pywintypes import com_error # __IGNORE_WARNING__ 665 from pywintypes import com_error # __IGNORE_WARNING__
640 except ImportError: 666 except ImportError:
641 # links were not created by install.py 667 # links were not created by install.py
642 return 668 return
643 669
644 regPath = ( 670 regPath = (
645 "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer" + 671 "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer"
646 "\\User Shell Folders" 672 + "\\User Shell Folders"
647 ) 673 )
648 674
649 if doCleanDesktopLinks or forceCleanDesktopLinks: 675 if doCleanDesktopLinks or forceCleanDesktopLinks:
650 # 1. cleanup desktop links 676 # 1. cleanup desktop links
651 regName = "Desktop" 677 regName = "Desktop"
652 desktopEntry = getWinregEntry(regName, regPath) 678 desktopEntry = getWinregEntry(regName, regPath)
653 if desktopEntry: 679 if desktopEntry:
658 try: 684 try:
659 os.remove(linkPath) 685 os.remove(linkPath)
660 except OSError: 686 except OSError:
661 # maybe restrictions prohibited link removal 687 # maybe restrictions prohibited link removal
662 print("Could not remove '{0}'.".format(linkPath)) 688 print("Could not remove '{0}'.".format(linkPath))
663 689
664 # 2. cleanup start menu entry 690 # 2. cleanup start menu entry
665 regName = "Programs" 691 regName = "Programs"
666 programsEntry = getWinregEntry(regName, regPath) 692 programsEntry = getWinregEntry(regName, regPath)
667 if programsEntry: 693 if programsEntry:
668 programsFolder = os.path.normpath(os.path.expandvars(programsEntry)) 694 programsFolder = os.path.normpath(os.path.expandvars(programsEntry))
676 702
677 703
678 def shutilCopy(src, dst, perm=0o644): 704 def shutilCopy(src, dst, perm=0o644):
679 """ 705 """
680 Wrapper function around shutil.copy() to ensure the permissions. 706 Wrapper function around shutil.copy() to ensure the permissions.
681 707
682 @param src source file name (string) 708 @param src source file name (string)
683 @param dst destination file name or directory name (string) 709 @param dst destination file name or directory name (string)
684 @param perm permissions to be set (integer) 710 @param perm permissions to be set (integer)
685 """ 711 """
686 shutil.copy(src, dst) 712 shutil.copy(src, dst)
690 716
691 717
692 def installEric(): 718 def installEric():
693 """ 719 """
694 Actually perform the installation steps. 720 Actually perform the installation steps.
695 721
696 @return result code (integer) 722 @return result code (integer)
697 """ 723 """
698 global distDir, doCleanup, cfg, progLanguages, sourceDir, configName 724 global distDir, doCleanup, cfg, progLanguages, sourceDir, configName
699 global installApis 725 global installApis
700 726
701 # Create the platform specific wrappers. 727 # Create the platform specific wrappers.
702 scriptsDir = "install_scripts" 728 scriptsDir = "install_scripts"
703 if not os.path.isdir(scriptsDir): 729 if not os.path.isdir(scriptsDir):
704 os.mkdir(scriptsDir) 730 os.mkdir(scriptsDir)
705 wnames = [] 731 wnames = []
706 for name in ["eric7_api", "eric7_doc"]: 732 for name in ["eric7_api", "eric7_doc"]:
707 wnames.append(createPyWrapper(cfg['ericDir'], name, scriptsDir, False)) 733 wnames.append(createPyWrapper(cfg["ericDir"], name, scriptsDir, False))
708 for name in ["eric7_browser", "eric7_compare", "eric7_configure", 734 for name in [
709 "eric7_diff", "eric7_editor", "eric7_hexeditor", 735 "eric7_browser",
710 "eric7_iconeditor", "eric7_plugininstall", 736 "eric7_compare",
711 "eric7_pluginrepository", "eric7_pluginuninstall", 737 "eric7_configure",
712 "eric7_qregularexpression", "eric7_re", "eric7_shell", 738 "eric7_diff",
713 "eric7_snap", "eric7_sqlbrowser", "eric7_tray", 739 "eric7_editor",
714 "eric7_trpreviewer", "eric7_uipreviewer", "eric7_testing", 740 "eric7_hexeditor",
715 "eric7_virtualenv", "eric7"]: 741 "eric7_iconeditor",
716 wnames.append(createPyWrapper(cfg['ericDir'], name, scriptsDir)) 742 "eric7_plugininstall",
717 743 "eric7_pluginrepository",
744 "eric7_pluginuninstall",
745 "eric7_qregularexpression",
746 "eric7_re",
747 "eric7_shell",
748 "eric7_snap",
749 "eric7_sqlbrowser",
750 "eric7_tray",
751 "eric7_trpreviewer",
752 "eric7_uipreviewer",
753 "eric7_testing",
754 "eric7_virtualenv",
755 "eric7",
756 ]:
757 wnames.append(createPyWrapper(cfg["ericDir"], name, scriptsDir))
758
718 # set install prefix, if not None 759 # set install prefix, if not None
719 if distDir: 760 if distDir:
720 for key in list(cfg.keys()): 761 for key in list(cfg.keys()):
721 cfg[key] = os.path.normpath( 762 cfg[key] = os.path.normpath(os.path.join(distDir, cfg[key].lstrip(os.sep)))
722 os.path.join(distDir, cfg[key].lstrip(os.sep))) 763
723
724 try: 764 try:
725 # Install the files 765 # Install the files
726 # make the install directories 766 # make the install directories
727 for key in cfg: 767 for key in cfg:
728 if cfg[key] and not os.path.isdir(cfg[key]): 768 if cfg[key] and not os.path.isdir(cfg[key]):
729 os.makedirs(cfg[key]) 769 os.makedirs(cfg[key])
730 770
731 # copy the eric config file 771 # copy the eric config file
732 if distDir: 772 if distDir:
733 shutilCopy(configName, cfg['mdir']) 773 shutilCopy(configName, cfg["mdir"])
734 if os.path.exists(configName + 'c'): 774 if os.path.exists(configName + "c"):
735 shutilCopy(configName + 'c', cfg['mdir']) 775 shutilCopy(configName + "c", cfg["mdir"])
736 else: 776 else:
737 shutilCopy(configName, modDir) 777 shutilCopy(configName, modDir)
738 if os.path.exists(configName + 'c'): 778 if os.path.exists(configName + "c"):
739 shutilCopy(configName + 'c', modDir) 779 shutilCopy(configName + "c", modDir)
740 780
741 # copy the various parts of eric 781 # copy the various parts of eric
742 copyTree( 782 copyTree(
743 eric7SourceDir, cfg['ericDir'], 783 eric7SourceDir,
744 ['*.py', '*.pyc', '*.pyo', '*.pyw'], 784 cfg["ericDir"],
745 [os.path.join(sourceDir, "Examples"), 785 ["*.py", "*.pyc", "*.pyo", "*.pyw"],
746 os.path.join(sourceDir, ".ropeproject")], 786 excludePatterns=["eric7config.py*"],
747 excludePatterns=["eric7config.py*"]) 787 )
748 copyTree( 788 copyTree(
749 os.path.join(eric7SourceDir, "Plugins"), 789 os.path.join(eric7SourceDir, "Plugins"),
750 os.path.join(cfg['ericDir'], "Plugins"), 790 os.path.join(cfg["ericDir"], "Plugins"),
751 ['*.svgz', '*.svg', '*.png', '*.style', '*.tmpl', '*.txt']) 791 ["*.svgz", "*.svg", "*.png", "*.style", "*.tmpl", "*.txt"],
792 )
752 copyTree( 793 copyTree(
753 os.path.join(eric7SourceDir, "Documentation"), 794 os.path.join(eric7SourceDir, "Documentation"),
754 cfg['ericDocDir'], 795 cfg["ericDocDir"],
755 ['*.html', '*.qch']) 796 ["*.html", "*.qch"],
756 copyTree( 797 )
757 os.path.join(eric7SourceDir, "CSSs"), 798 copyTree(os.path.join(eric7SourceDir, "CSSs"), cfg["ericCSSDir"], ["*.css"])
758 cfg['ericCSSDir'],
759 ['*.css'])
760 copyTree( 799 copyTree(
761 os.path.join(eric7SourceDir, "Styles"), 800 os.path.join(eric7SourceDir, "Styles"),
762 cfg['ericStylesDir'], 801 cfg["ericStylesDir"],
763 ['*.qss', '*.ehj']) 802 ["*.qss", "*.ehj"],
803 )
764 copyTree( 804 copyTree(
765 os.path.join(eric7SourceDir, "Themes"), 805 os.path.join(eric7SourceDir, "Themes"), cfg["ericThemesDir"], ["*.ethj"]
766 cfg['ericThemesDir'], 806 )
767 ['*.ethj'])
768 copyTree( 807 copyTree(
769 os.path.join(eric7SourceDir, "i18n"), 808 os.path.join(eric7SourceDir, "i18n"), cfg["ericTranslationsDir"], ["*.qm"]
770 cfg['ericTranslationsDir'], 809 )
771 ['*.qm'])
772 copyTree( 810 copyTree(
773 os.path.join(eric7SourceDir, "icons"), 811 os.path.join(eric7SourceDir, "icons"),
774 cfg['ericIconDir'], 812 cfg["ericIconDir"],
775 ['*.svgz', '*.svg', '*.png', 'LICENSE*.*', 'readme.txt']) 813 ["*.svgz", "*.svg", "*.png", "LICENSE*.*", "readme.txt"],
814 )
776 copyTree( 815 copyTree(
777 os.path.join(eric7SourceDir, "pixmaps"), 816 os.path.join(eric7SourceDir, "pixmaps"),
778 cfg['ericPixDir'], 817 cfg["ericPixDir"],
779 ['*.svgz', '*.svg', '*.png', '*.xpm', '*.ico', '*.gif']) 818 ["*.svgz", "*.svg", "*.png", "*.xpm", "*.ico", "*.gif"],
819 )
780 copyTree( 820 copyTree(
781 os.path.join(eric7SourceDir, "DesignerTemplates"), 821 os.path.join(eric7SourceDir, "DesignerTemplates"),
782 cfg['ericTemplatesDir'], 822 cfg["ericTemplatesDir"],
783 ['*.tmpl']) 823 ["*.tmpl"],
824 )
784 copyTree( 825 copyTree(
785 os.path.join(eric7SourceDir, "CodeTemplates"), 826 os.path.join(eric7SourceDir, "CodeTemplates"),
786 cfg['ericCodeTemplatesDir'], 827 cfg["ericCodeTemplatesDir"],
787 ['*.tmpl']) 828 ["*.tmpl"],
829 )
788 copyTree( 830 copyTree(
789 os.path.join(eric7SourceDir, "DebugClients", "Python", "coverage"), 831 os.path.join(eric7SourceDir, "DebugClients", "Python", "coverage"),
790 os.path.join(cfg['ericDir'], "DebugClients", "Python", "coverage"), 832 os.path.join(cfg["ericDir"], "DebugClients", "Python", "coverage"),
791 ['*.js', '*.html', '*.png', '*.css', '*.scss', '*.txt', '*.rst']) 833 ["*.js", "*.html", "*.png", "*.css", "*.scss", "*.txt", "*.rst"],
792 834 )
835
793 # copy some data files needed at various places 836 # copy some data files needed at various places
794 copyTree( 837 copyTree(
795 os.path.join(eric7SourceDir, "data"), 838 os.path.join(eric7SourceDir, "data"),
796 os.path.join(cfg['ericDir'], "data"), 839 os.path.join(cfg["ericDir"], "data"),
797 ['*.txt']) 840 ["*.txt"],
841 )
798 copyTree( 842 copyTree(
799 os.path.join(eric7SourceDir, "EricNetwork", "data"), 843 os.path.join(eric7SourceDir, "EricNetwork", "data"),
800 os.path.join(cfg['ericDir'], "EricNetwork", "data"), 844 os.path.join(cfg["ericDir"], "EricNetwork", "data"),
801 ['*.dat', '*.txt']) 845 ["*.dat", "*.txt"],
846 )
802 copyTree( 847 copyTree(
803 os.path.join(eric7SourceDir, "IconEditor", "cursors"), 848 os.path.join(eric7SourceDir, "IconEditor", "cursors"),
804 os.path.join(cfg['ericDir'], "IconEditor", "cursors"), 849 os.path.join(cfg["ericDir"], "IconEditor", "cursors"),
805 ['*.xpm']) 850 ["*.xpm"],
851 )
806 copyTree( 852 copyTree(
807 os.path.join(eric7SourceDir, "UI", "data"), 853 os.path.join(eric7SourceDir, "UI", "data"),
808 os.path.join(cfg['ericDir'], "UI", "data"), 854 os.path.join(cfg["ericDir"], "UI", "data"),
809 ['*.css']) 855 ["*.css"],
856 )
810 copyTree( 857 copyTree(
811 os.path.join(eric7SourceDir, "WebBrowser"), 858 os.path.join(eric7SourceDir, "WebBrowser"),
812 os.path.join(cfg['ericDir'], "WebBrowser"), 859 os.path.join(cfg["ericDir"], "WebBrowser"),
813 ['*.xbel', '*.xml', '*.html', '*.png', '*.gif', '*.js']) 860 ["*.xbel", "*.xml", "*.html", "*.png", "*.gif", "*.js"],
814 861 )
862
815 # copy the wrappers 863 # copy the wrappers
816 for wname in wnames: 864 for wname in wnames:
817 shutilCopy(wname, cfg['bindir'], perm=0o755) 865 shutilCopy(wname, cfg["bindir"], perm=0o755)
818 os.remove(wname) 866 os.remove(wname)
819 shutil.rmtree(scriptsDir) 867 shutil.rmtree(scriptsDir)
820 868
821 # copy the license file 869 # copy the license file
822 shutilCopy(os.path.join(sourceDir, "docs", "LICENSE.GPL3"), 870 shutilCopy(os.path.join(sourceDir, "docs", "LICENSE.GPL3"), cfg["ericDir"])
823 cfg['ericDir']) 871
824
825 # create the global plugins directory 872 # create the global plugins directory
826 createGlobalPluginsDir() 873 createGlobalPluginsDir()
827 874
828 except OSError as msg: 875 except OSError as msg:
829 sys.stderr.write( 876 sys.stderr.write("Error: {0}\nTry install with admin rights.\n".format(msg))
830 'Error: {0}\nTry install with admin rights.\n'.format(msg)) 877 return 7
831 return(7) 878
832
833 # copy some text files to the doc area 879 # copy some text files to the doc area
834 for name in ["LICENSE.GPL3", "THANKS", "changelog"]: 880 for name in ["LICENSE.GPL3", "THANKS", "changelog"]:
835 try: 881 try:
836 shutilCopy(os.path.join(sourceDir, "docs", name), 882 shutilCopy(os.path.join(sourceDir, "docs", name), cfg["ericDocDir"])
837 cfg['ericDocDir'])
838 except OSError: 883 except OSError:
839 print("Could not install '{0}'.".format( 884 print(
840 os.path.join(sourceDir, "docs", name))) 885 "Could not install '{0}'.".format(os.path.join(sourceDir, "docs", name))
841 for name in glob.glob(os.path.join(sourceDir, 'docs', 'README*.*')): 886 )
887 for name in glob.glob(os.path.join(sourceDir, "docs", "README*.*")):
842 try: 888 try:
843 shutilCopy(name, cfg['ericDocDir']) 889 shutilCopy(name, cfg["ericDocDir"])
844 except OSError: 890 except OSError:
845 print("Could not install '{0}'.".format(name)) 891 print("Could not install '{0}'.".format(name))
846 892
847 # copy some more stuff 893 # copy some more stuff
848 for name in ('default.ekj', 'default_Mac.ekj', 894 for name in ("default.ekj", "default_Mac.ekj", "default.e4k", "default_Mac.e4k"):
849 'default.e4k', 'default_Mac.e4k'):
850 try: 895 try:
851 shutilCopy(os.path.join(sourceDir, "others", name), 896 shutilCopy(os.path.join(sourceDir, "others", name), cfg["ericOthersDir"])
852 cfg['ericOthersDir'])
853 except OSError: 897 except OSError:
854 print("Could not install '{0}'.".format( 898 print(
855 os.path.join(sourceDir, "others", name))) 899 "Could not install '{0}'.".format(
856 900 os.path.join(sourceDir, "others", name)
901 )
902 )
903
857 # install the API file 904 # install the API file
858 if installApis: 905 if installApis:
859 for progLanguage in progLanguages: 906 for progLanguage in progLanguages:
860 apidir = os.path.join(cfg['apidir'], progLanguage.lower()) 907 apidir = os.path.join(cfg["apidir"], progLanguage.lower())
861 print("Installing {0} API files to '{1}'.".format( 908 print("Installing {0} API files to '{1}'.".format(progLanguage, apidir))
862 progLanguage, apidir))
863 if not os.path.exists(apidir): 909 if not os.path.exists(apidir):
864 os.makedirs(apidir) 910 os.makedirs(apidir)
865 for apiName in glob.glob(os.path.join(eric7SourceDir, "APIs", 911 for apiName in glob.glob(
866 progLanguage, "*.api")): 912 os.path.join(eric7SourceDir, "APIs", progLanguage, "*.api")
913 ):
867 try: 914 try:
868 shutilCopy(apiName, apidir) 915 shutilCopy(apiName, apidir)
869 except OSError: 916 except OSError:
870 print("Could not install '{0}' (no permission)." 917 print("Could not install '{0}' (no permission).".format(apiName))
871 .format(apiName)) 918 for apiName in glob.glob(
872 for apiName in glob.glob(os.path.join(eric7SourceDir, "APIs", 919 os.path.join(eric7SourceDir, "APIs", progLanguage, "*.bas")
873 progLanguage, "*.bas")): 920 ):
874 try: 921 try:
875 shutilCopy(apiName, apidir) 922 shutilCopy(apiName, apidir)
876 except OSError: 923 except OSError:
877 print("Could not install '{0}' (no permission)." 924 print("Could not install '{0}' (no permission).".format(apiName))
878 .format(apiName))
879 if progLanguage == "Python": 925 if progLanguage == "Python":
880 # copy Python3 API files to the same destination 926 # copy Python3 API files to the same destination
881 for apiName in glob.glob(os.path.join(eric7SourceDir, "APIs", 927 for apiName in glob.glob(
882 "Python3", "*.api")): 928 os.path.join(eric7SourceDir, "APIs", "Python3", "*.api")
929 ):
883 try: 930 try:
884 shutilCopy(apiName, apidir) 931 shutilCopy(apiName, apidir)
885 except OSError: 932 except OSError:
886 print("Could not install '{0}' (no permission)." 933 print(
887 .format(apiName)) 934 "Could not install '{0}' (no permission).".format(apiName)
888 for apiName in glob.glob(os.path.join(eric7SourceDir, "APIs", 935 )
889 "Python3", "*.bas")): 936 for apiName in glob.glob(
890 if os.path.exists(os.path.join( 937 os.path.join(eric7SourceDir, "APIs", "Python3", "*.bas")
891 apidir, os.path.basename( 938 ):
892 apiName.replace(".bas", ".api")))): 939 if os.path.exists(
940 os.path.join(
941 apidir, os.path.basename(apiName.replace(".bas", ".api"))
942 )
943 ):
893 try: 944 try:
894 shutilCopy(apiName, apidir) 945 shutilCopy(apiName, apidir)
895 except OSError: 946 except OSError:
896 print("Could not install '{0}' (no permission)." 947 print(
897 .format(apiName)) 948 "Could not install '{0}' (no permission).".format(
898 949 apiName
950 )
951 )
952
899 # copy MicroPython API files to the same destination 953 # copy MicroPython API files to the same destination
900 for apiName in glob.glob(os.path.join(eric7SourceDir, "APIs", 954 for apiName in glob.glob(
901 "MicroPython", "*.api")): 955 os.path.join(eric7SourceDir, "APIs", "MicroPython", "*.api")
956 ):
902 try: 957 try:
903 shutilCopy(apiName, apidir) 958 shutilCopy(apiName, apidir)
904 except OSError: 959 except OSError:
905 print("Could not install '{0}' (no permission)." 960 print(
906 .format(apiName)) 961 "Could not install '{0}' (no permission).".format(apiName)
907 for apiName in glob.glob(os.path.join(eric7SourceDir, "APIs", 962 )
908 "MicroPython", "*.bas")): 963 for apiName in glob.glob(
909 if os.path.exists(os.path.join( 964 os.path.join(eric7SourceDir, "APIs", "MicroPython", "*.bas")
910 apidir, os.path.basename( 965 ):
911 apiName.replace(".bas", ".api")))): 966 if os.path.exists(
967 os.path.join(
968 apidir, os.path.basename(apiName.replace(".bas", ".api"))
969 )
970 ):
912 try: 971 try:
913 shutilCopy(apiName, apidir) 972 shutilCopy(apiName, apidir)
914 except OSError: 973 except OSError:
915 print("Could not install '{0}' (no permission)." 974 print(
916 .format(apiName)) 975 "Could not install '{0}' (no permission).".format(
917 976 apiName
977 )
978 )
979
918 # Create menu entry for Linux systems 980 # Create menu entry for Linux systems
919 if sys.platform.startswith("linux"): 981 if sys.platform.startswith("linux"):
920 createLinuxSpecifics() 982 createLinuxSpecifics()
921 983
922 # Create Desktop and Start Menu entries for Windows systems 984 # Create Desktop and Start Menu entries for Windows systems
923 elif sys.platform.startswith(("win", "cygwin")): 985 elif sys.platform.startswith(("win", "cygwin")):
924 createWindowsLinks() 986 createWindowsLinks()
925 987
926 # Create a Mac application bundle 988 # Create a Mac application bundle
927 elif sys.platform == "darwin": 989 elif sys.platform == "darwin":
928 createMacAppBundle(cfg['ericDir']) 990 createMacAppBundle(cfg["ericDir"])
929 991
930 return 0 992 return 0
931 993
932 994
933 def createLinuxSpecifics(): 995 def createLinuxSpecifics():
934 """ 996 """
935 Install Linux specific files. 997 Install Linux specific files.
936 """ 998 """
937 global distDir, sourceDir 999 global distDir, sourceDir
938 1000
1001 dataSourceDir = os.path.join(eric7SourceDir, "data", "linux")
1002
939 if distDir: 1003 if distDir:
940 dst = os.path.normpath(os.path.join(distDir, "usr/share/icons")) 1004 dst = os.path.normpath(os.path.join(distDir, "usr/share/icons"))
941 if not os.path.exists(dst): 1005 if not os.path.exists(dst):
942 os.makedirs(dst) 1006 os.makedirs(dst)
943 shutilCopy( 1007 shutilCopy(
944 os.path.join(eric7SourceDir, "pixmaps", "eric_icon.png"), 1008 os.path.join(eric7SourceDir, "pixmaps", "eric_icon.png"),
945 os.path.join(dst, "eric.png")) 1009 os.path.join(dst, "eric.png"),
1010 )
946 shutilCopy( 1011 shutilCopy(
947 os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"), 1012 os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"),
948 os.path.join(dst, "ericWeb.png")) 1013 os.path.join(dst, "ericWeb.png"),
949 1014 )
1015
950 dst = os.path.normpath( 1016 dst = os.path.normpath(
951 os.path.join(distDir, "usr/share/icons/hicolor/48x48/apps")) 1017 os.path.join(distDir, "usr/share/icons/hicolor/48x48/apps")
1018 )
952 if not os.path.exists(dst): 1019 if not os.path.exists(dst):
953 os.makedirs(dst) 1020 os.makedirs(dst)
954 shutilCopy( 1021 shutilCopy(
955 os.path.join(eric7SourceDir, "pixmaps", "eric48_icon.png"), 1022 os.path.join(eric7SourceDir, "pixmaps", "eric48_icon.png"),
956 os.path.join(dst, "eric.png")) 1023 os.path.join(dst, "eric.png"),
1024 )
957 shutilCopy( 1025 shutilCopy(
958 os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"), 1026 os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"),
959 os.path.join(dst, "ericWeb.png")) 1027 os.path.join(dst, "ericWeb.png"),
960 1028 )
961 dst = os.path.normpath( 1029
962 os.path.join(distDir, "usr/share/applications")) 1030 dst = os.path.normpath(os.path.join(distDir, "usr/share/applications"))
963 if not os.path.exists(dst): 1031 if not os.path.exists(dst):
964 os.makedirs(dst) 1032 os.makedirs(dst)
965 copyDesktopFile(os.path.join(sourceDir, "linux", "eric7.desktop.in"),
966 os.path.join(dst, "eric7.desktop"))
967 copyDesktopFile( 1033 copyDesktopFile(
968 os.path.join(sourceDir, "linux", "eric7_browser.desktop.in"), 1034 os.path.join(dataSourceDir, "eric7.desktop.in"),
969 os.path.join(dst, "eric7_browser.desktop")) 1035 os.path.join(dst, "eric7.desktop"),
970 1036 )
971 dst = os.path.normpath( 1037 copyDesktopFile(
972 os.path.join(distDir, "usr/share/metainfo")) 1038 os.path.join(dataSourceDir, "eric7_browser.desktop.in"),
1039 os.path.join(dst, "eric7_browser.desktop"),
1040 )
1041
1042 dst = os.path.normpath(os.path.join(distDir, "usr/share/metainfo"))
973 if not os.path.exists(dst): 1043 if not os.path.exists(dst):
974 os.makedirs(dst) 1044 os.makedirs(dst)
975 copyAppStreamFile( 1045 copyAppStreamFile(
976 os.path.join(sourceDir, "linux", "eric7.appdata.xml.in"), 1046 os.path.join(dataSourceDir, "eric7.appdata.xml.in"),
977 os.path.join(dst, "eric7.appdata.xml")) 1047 os.path.join(dst, "eric7.appdata.xml"),
1048 )
978 elif os.getuid() == 0: 1049 elif os.getuid() == 0:
979 shutilCopy( 1050 shutilCopy(
980 os.path.join(eric7SourceDir, "pixmaps", "eric_icon.png"), 1051 os.path.join(eric7SourceDir, "pixmaps", "eric_icon.png"),
981 "/usr/share/icons/eric.png") 1052 "/usr/share/icons/eric.png",
1053 )
982 shutilCopy( 1054 shutilCopy(
983 os.path.join(eric7SourceDir, "pixmaps", "eric48_icon.png"), 1055 os.path.join(eric7SourceDir, "pixmaps", "eric48_icon.png"),
984 "/usr/share/icons/hicolor/48x48/apps/eric.png") 1056 "/usr/share/icons/hicolor/48x48/apps/eric.png",
1057 )
985 copyDesktopFile( 1058 copyDesktopFile(
986 os.path.join(sourceDir, "linux", "eric7.desktop.in"), 1059 os.path.join(dataSourceDir, "eric7.desktop.in"),
987 "/usr/share/applications/eric7.desktop") 1060 "/usr/share/applications/eric7.desktop",
1061 )
988 if os.path.exists("/usr/share/metainfo"): 1062 if os.path.exists("/usr/share/metainfo"):
989 copyAppStreamFile( 1063 copyAppStreamFile(
990 os.path.join(sourceDir, "linux", "eric7.appdata.xml.in"), 1064 os.path.join(dataSourceDir, "eric7.appdata.xml.in"),
991 "/usr/share/metainfo/eric7.appdata.xml") 1065 "/usr/share/metainfo/eric7.appdata.xml",
1066 )
992 elif os.path.exists("/usr/share/appdata"): 1067 elif os.path.exists("/usr/share/appdata"):
993 copyAppStreamFile( 1068 copyAppStreamFile(
994 os.path.join(sourceDir, "linux", "eric7.appdata.xml.in"), 1069 os.path.join(dataSourceDir, "eric7.appdata.xml.in"),
995 "/usr/share/appdata/eric7.appdata.xml") 1070 "/usr/share/appdata/eric7.appdata.xml",
1071 )
996 shutilCopy( 1072 shutilCopy(
997 os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"), 1073 os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"),
998 "/usr/share/icons/ericWeb.png") 1074 "/usr/share/icons/ericWeb.png",
1075 )
999 shutilCopy( 1076 shutilCopy(
1000 os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"), 1077 os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"),
1001 "/usr/share/icons/hicolor/48x48/apps/ericWeb.png") 1078 "/usr/share/icons/hicolor/48x48/apps/ericWeb.png",
1079 )
1002 copyDesktopFile( 1080 copyDesktopFile(
1003 os.path.join(sourceDir, "linux", "eric7_browser.desktop.in"), 1081 os.path.join(dataSourceDir, "eric7_browser.desktop.in"),
1004 "/usr/share/applications/eric7_browser.desktop") 1082 "/usr/share/applications/eric7_browser.desktop",
1083 )
1005 elif os.getuid() >= 1000: 1084 elif os.getuid() >= 1000:
1006 # it is assumed, that user ids start at 1000 1085 # it is assumed, that user ids start at 1000
1007 localPath = os.path.join(os.path.expanduser("~"), 1086 localPath = os.path.join(os.path.expanduser("~"), ".local", "share")
1008 ".local", "share")
1009 # create directories first 1087 # create directories first
1010 for directory in [os.path.join(localPath, name) 1088 for directory in [
1011 for name in ("icons", "icons/hicolor/48x48/apps", 1089 os.path.join(localPath, name)
1012 "applications", "metainfo", "appdata")]: 1090 for name in (
1091 "icons",
1092 "icons/hicolor/48x48/apps",
1093 "applications",
1094 "metainfo",
1095 "appdata",
1096 )
1097 ]:
1013 if not os.path.isdir(directory): 1098 if not os.path.isdir(directory):
1014 os.makedirs(directory) 1099 os.makedirs(directory)
1015 # now copy the files 1100 # now copy the files
1016 shutilCopy( 1101 shutilCopy(
1017 os.path.join(eric7SourceDir, "pixmaps", "eric_icon.png"), 1102 os.path.join(eric7SourceDir, "pixmaps", "eric_icon.png"),
1018 os.path.join(localPath, "icons", "eric.png")) 1103 os.path.join(localPath, "icons", "eric.png"),
1104 )
1019 shutilCopy( 1105 shutilCopy(
1020 os.path.join(eric7SourceDir, "pixmaps", "eric48_icon.png"), 1106 os.path.join(eric7SourceDir, "pixmaps", "eric48_icon.png"),
1021 os.path.join(localPath, "icons/hicolor/48x48/apps", "eric.png")) 1107 os.path.join(localPath, "icons/hicolor/48x48/apps", "eric.png"),
1108 )
1022 copyDesktopFile( 1109 copyDesktopFile(
1023 os.path.join(sourceDir, "linux", "eric7.desktop.in"), 1110 os.path.join(dataSourceDir, "eric7.desktop.in"),
1024 os.path.join(localPath, "applications", "eric7.desktop")) 1111 os.path.join(localPath, "applications", "eric7.desktop"),
1112 )
1025 copyAppStreamFile( 1113 copyAppStreamFile(
1026 os.path.join(sourceDir, "linux", "eric7.appdata.xml.in"), 1114 os.path.join(dataSourceDir, "eric7.appdata.xml.in"),
1027 os.path.join(localPath, "metainfo", "eric7.appdata.xml")) 1115 os.path.join(localPath, "metainfo", "eric7.appdata.xml"),
1116 )
1028 copyAppStreamFile( 1117 copyAppStreamFile(
1029 os.path.join(sourceDir, "linux", "eric7.appdata.xml.in"), 1118 os.path.join(dataSourceDir, "eric7.appdata.xml.in"),
1030 os.path.join(localPath, "appdata", "eric7.appdata.xml")) 1119 os.path.join(localPath, "appdata", "eric7.appdata.xml"),
1120 )
1031 shutilCopy( 1121 shutilCopy(
1032 os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"), 1122 os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"),
1033 os.path.join(localPath, "icons", "ericWeb.png")) 1123 os.path.join(localPath, "icons", "ericWeb.png"),
1124 )
1034 shutilCopy( 1125 shutilCopy(
1035 os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"), 1126 os.path.join(eric7SourceDir, "pixmaps", "ericWeb48_icon.png"),
1036 os.path.join(localPath, "icons/hicolor/48x48/apps", "ericWeb.png")) 1127 os.path.join(localPath, "icons/hicolor/48x48/apps", "ericWeb.png"),
1128 )
1037 copyDesktopFile( 1129 copyDesktopFile(
1038 os.path.join(sourceDir, "linux", "eric7_browser.desktop.in"), 1130 os.path.join(dataSourceDir, "eric7_browser.desktop.in"),
1039 os.path.join(localPath, "applications", 1131 os.path.join(localPath, "applications", "eric7_browser.desktop"),
1040 "eric7_browser.desktop")) 1132 )
1041 1133
1042 1134
1043 def createWindowsLinks(): 1135 def createWindowsLinks():
1044 """ 1136 """
1045 Create Desktop and Start Menu links. 1137 Create Desktop and Start Menu links.
1046 """ 1138 """
1047 try: 1139 try:
1048 # check, if pywin32 is available 1140 # check, if pywin32 is available
1049 from win32com.client import Dispatch # __IGNORE_WARNING__ 1141 from win32com.client import Dispatch # __IGNORE_WARNING__
1050 except ImportError: 1142 except ImportError:
1051 installed = pipInstall( 1143 installed = pipInstall(
1052 "pywin32", 1144 "pywin32",
1053 "\nThe Python package 'pywin32' could not be imported.", 1145 "\nThe Python package 'pywin32' could not be imported.",
1054 force=False 1146 force=False,
1055 ) 1147 )
1056 if installed: 1148 if installed:
1057 # create the links via an external script to get around some 1149 # create the links via an external script to get around some
1058 # startup magic done by pywin32.pth 1150 # startup magic done by pywin32.pth
1059 args = [ 1151 args = [
1060 sys.executable, 1152 sys.executable,
1061 os.path.join(os.path.dirname(__file__), 1153 os.path.join(os.path.dirname(__file__), "create_windows_links.py"),
1062 "create_windows_links.py"),
1063 ] 1154 ]
1064 subprocess.run(args) # secok 1155 subprocess.run(args) # secok
1065 else: 1156 else:
1066 print( 1157 print(
1067 "\nThe Python package 'pywin32' is not installed. Desktop and" 1158 "\nThe Python package 'pywin32' is not installed. Desktop and"
1068 " Start Menu entries will not be created." 1159 " Start Menu entries will not be created."
1069 ) 1160 )
1070 return 1161 return
1071 1162
1072 regPath = ( 1163 regPath = (
1073 "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer" + 1164 "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer"
1074 "\\User Shell Folders" 1165 + "\\User Shell Folders"
1075 ) 1166 )
1076 1167
1077 # 1. create desktop shortcuts 1168 # 1. create desktop shortcuts
1078 regName = "Desktop" 1169 regName = "Desktop"
1079 desktopEntry = getWinregEntry(regName, regPath) 1170 desktopEntry = getWinregEntry(regName, regPath)
1080 if desktopEntry: 1171 if desktopEntry:
1081 desktopFolder = os.path.normpath(os.path.expandvars(desktopEntry)) 1172 desktopFolder = os.path.normpath(os.path.expandvars(desktopEntry))
1082 for linkName, targetPath, iconPath in windowsDesktopEntries(): 1173 for linkName, targetPath, iconPath in windowsDesktopEntries():
1083 linkPath = os.path.join(desktopFolder, linkName) 1174 linkPath = os.path.join(desktopFolder, linkName)
1084 createWindowsShortcut(linkPath, targetPath, iconPath) 1175 createWindowsShortcut(linkPath, targetPath, iconPath)
1085 1176
1086 # 2. create start menu entry and shortcuts 1177 # 2. create start menu entry and shortcuts
1087 regName = "Programs" 1178 regName = "Programs"
1088 programsEntry = getWinregEntry(regName, regPath) 1179 programsEntry = getWinregEntry(regName, regPath)
1089 if programsEntry: 1180 if programsEntry:
1090 programsFolder = os.path.normpath(os.path.expandvars(programsEntry)) 1181 programsFolder = os.path.normpath(os.path.expandvars(programsEntry))
1093 try: 1184 try:
1094 os.makedirs(eric7EntryPath) 1185 os.makedirs(eric7EntryPath)
1095 except OSError: 1186 except OSError:
1096 # maybe restrictions prohibited link creation 1187 # maybe restrictions prohibited link creation
1097 return 1188 return
1098 1189
1099 for linkName, targetPath, iconPath in windowsDesktopEntries(): 1190 for linkName, targetPath, iconPath in windowsDesktopEntries():
1100 linkPath = os.path.join(eric7EntryPath, linkName) 1191 linkPath = os.path.join(eric7EntryPath, linkName)
1101 createWindowsShortcut(linkPath, targetPath, iconPath) 1192 createWindowsShortcut(linkPath, targetPath, iconPath)
1102 1193
1103 1194
1108 @param pydir the name of the directory where the Python script will 1199 @param pydir the name of the directory where the Python script will
1109 eventually be installed 1200 eventually be installed
1110 @type str 1201 @type str
1111 """ 1202 """
1112 global cfg, macAppBundleName, macPythonExe, macAppBundlePath 1203 global cfg, macAppBundleName, macPythonExe, macAppBundlePath
1113 1204
1114 directories = { 1205 directories = {
1115 "contents": "{0}/{1}/Contents/".format( 1206 "contents": "{0}/{1}/Contents/".format(macAppBundlePath, macAppBundleName),
1116 macAppBundlePath, macAppBundleName), 1207 "exe": "{0}/{1}/Contents/MacOS".format(macAppBundlePath, macAppBundleName),
1117 "exe": "{0}/{1}/Contents/MacOS".format( 1208 "icns": "{0}/{1}/Contents/Resources".format(macAppBundlePath, macAppBundleName),
1118 macAppBundlePath, macAppBundleName),
1119 "icns": "{0}/{1}/Contents/Resources".format(
1120 macAppBundlePath, macAppBundleName)
1121 } 1209 }
1122 for directory in directories.values(): 1210 for directory in directories.values():
1123 if not os.path.exists(directory): 1211 if not os.path.exists(directory):
1124 os.makedirs(directory) 1212 os.makedirs(directory)
1125 1213
1126 if macPythonExe == defaultMacPythonExe and macPythonExe: 1214 if macPythonExe == defaultMacPythonExe and macPythonExe:
1127 starter = os.path.join(directories["exe"], "eric") 1215 starter = os.path.join(directories["exe"], "eric")
1128 os.symlink(macPythonExe, starter) 1216 os.symlink(macPythonExe, starter)
1129 else: 1217 else:
1130 starter = "python{0}".format(sys.version_info.major) 1218 starter = "python{0}".format(sys.version_info.major)
1131 1219
1132 wname = os.path.join(directories["exe"], "eric7") 1220 wname = os.path.join(directories["exe"], "eric7")
1133 1221
1134 # determine entry for DYLD_FRAMEWORK_PATH 1222 # determine entry for DYLD_FRAMEWORK_PATH
1135 dyldLine = "" 1223 dyldLine = ""
1136 try: 1224 try:
1137 from PyQt6.QtCore import QLibraryInfo 1225 from PyQt6.QtCore import QLibraryInfo
1138 qtLibraryDir = QLibraryInfo.path( 1226
1139 QLibraryInfo.LibraryPath.LibrariesPath) 1227 qtLibraryDir = QLibraryInfo.path(QLibraryInfo.LibraryPath.LibrariesPath)
1140 except ImportError: 1228 except ImportError:
1141 qtLibraryDir = "" 1229 qtLibraryDir = ""
1142 if qtLibraryDir: 1230 if qtLibraryDir:
1143 dyldLine = "DYLD_FRAMEWORK_PATH={0}\n".format(qtLibraryDir) 1231 dyldLine = "DYLD_FRAMEWORK_PATH={0}\n".format(qtLibraryDir)
1144 1232
1145 # determine entry for PATH 1233 # determine entry for PATH
1146 pathLine = "" 1234 pathLine = ""
1147 path = os.getenv("PATH", "") 1235 path = os.getenv("PATH", "")
1148 if path: 1236 if path:
1149 pybin = os.path.join(sys.exec_prefix, "bin") 1237 pybin = os.path.join(sys.exec_prefix, "bin")
1151 pathlist_n = [pybin] 1239 pathlist_n = [pybin]
1152 for path_ in pathlist: 1240 for path_ in pathlist:
1153 if path_ and path_ not in pathlist_n: 1241 if path_ and path_ not in pathlist_n:
1154 pathlist_n.append(path_) 1242 pathlist_n.append(path_)
1155 pathLine = "PATH={0}\n".format(os.pathsep.join(pathlist_n)) 1243 pathLine = "PATH={0}\n".format(os.pathsep.join(pathlist_n))
1156 1244
1157 # create the wrapper script 1245 # create the wrapper script
1158 wrapper = ('''#!/bin/sh\n''' 1246 wrapper = (
1159 '''\n''' 1247 """#!/bin/sh\n"""
1160 '''{0}''' 1248 """\n"""
1161 '''{1}''' 1249 """{0}"""
1162 '''exec "{2}" "{3}/{4}.py" "$@"\n''' 1250 """{1}"""
1163 .format(pathLine, dyldLine, starter, pydir, "eric7")) 1251 """exec "{2}" "{3}/{4}.py" "$@"\n""".format(
1252 pathLine, dyldLine, starter, pydir, "eric7"
1253 )
1254 )
1164 copyToFile(wname, wrapper) 1255 copyToFile(wname, wrapper)
1165 os.chmod(wname, 0o755) # secok 1256 os.chmod(wname, 0o755) # secok
1166 1257
1167 shutilCopy(os.path.join(eric7SourceDir, "pixmaps", "eric_2.icns"), 1258 shutilCopy(
1168 os.path.join(directories["icns"], "eric.icns")) 1259 os.path.join(eric7SourceDir, "pixmaps", "eric_2.icns"),
1169 1260 os.path.join(directories["icns"], "eric.icns"),
1261 )
1262
1170 if os.path.exists(os.path.join("eric", "eric7", "UI", "Info.py")): 1263 if os.path.exists(os.path.join("eric", "eric7", "UI", "Info.py")):
1171 # Installing from archive 1264 # Installing from archive
1172 from eric.eric7.UI.Info import Version, CopyrightShort 1265 from eric.eric7.UI.Info import Version, CopyrightShort
1173 elif os.path.exists(os.path.join("eric7", "UI", "Info.py")): 1266 elif os.path.exists(os.path.join("eric7", "UI", "Info.py")):
1174 # Installing from source tree 1267 # Installing from source tree
1175 from eric7.UI.Info import Version, CopyrightShort 1268 from eric7.UI.Info import Version, CopyrightShort
1176 else: 1269 else:
1177 Version = "Unknown" 1270 Version = "Unknown"
1178 CopyrightShort = "(c) 2002 - 2022 Detlev Offenbach" 1271 CopyrightShort = "(c) 2002 - 2022 Detlev Offenbach"
1179 1272
1180 copyToFile( 1273 copyToFile(
1181 os.path.join(directories["contents"], "Info.plist"), 1274 os.path.join(directories["contents"], "Info.plist"),
1182 '''<?xml version="1.0" encoding="UTF-8"?>\n''' 1275 """<?xml version="1.0" encoding="UTF-8"?>\n"""
1183 '''<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"\n''' 1276 """<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"\n"""
1184 ''' "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n''' 1277 """ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n"""
1185 '''<plist version="1.0">\n''' 1278 """<plist version="1.0">\n"""
1186 '''<dict>\n''' 1279 """<dict>\n"""
1187 ''' <key>CFBundleExecutable</key>\n''' 1280 """ <key>CFBundleExecutable</key>\n"""
1188 ''' <string>eric7</string>\n''' 1281 """ <string>eric7</string>\n"""
1189 ''' <key>CFBundleIconFile</key>\n''' 1282 """ <key>CFBundleIconFile</key>\n"""
1190 ''' <string>eric.icns</string>\n''' 1283 """ <string>eric.icns</string>\n"""
1191 ''' <key>CFBundleInfoDictionaryVersion</key>\n''' 1284 """ <key>CFBundleInfoDictionaryVersion</key>\n"""
1192 ''' <string>{1}</string>\n''' 1285 """ <string>{1}</string>\n"""
1193 ''' <key>CFBundleName</key>\n''' 1286 """ <key>CFBundleName</key>\n"""
1194 ''' <string>{0}</string>\n''' 1287 """ <string>{0}</string>\n"""
1195 ''' <key>CFBundleDisplayName</key>\n''' 1288 """ <key>CFBundleDisplayName</key>\n"""
1196 ''' <string>{0}</string>\n''' 1289 """ <string>{0}</string>\n"""
1197 ''' <key>CFBundlePackageType</key>\n''' 1290 """ <key>CFBundlePackageType</key>\n"""
1198 ''' <string>APPL</string>\n''' 1291 """ <string>APPL</string>\n"""
1199 ''' <key>CFBundleSignature</key>\n''' 1292 """ <key>CFBundleSignature</key>\n"""
1200 ''' <string>ERIC-IDE</string>\n''' 1293 """ <string>ERIC-IDE</string>\n"""
1201 ''' <key>CFBundleVersion</key>\n''' 1294 """ <key>CFBundleVersion</key>\n"""
1202 ''' <string>{1}</string>\n''' 1295 """ <string>{1}</string>\n"""
1203 ''' <key>CFBundleGetInfoString</key>\n''' 1296 """ <key>CFBundleGetInfoString</key>\n"""
1204 ''' <string>{1}, {2}</string>\n''' 1297 """ <string>{1}, {2}</string>\n"""
1205 ''' <key>CFBundleIdentifier</key>\n''' 1298 """ <key>CFBundleIdentifier</key>\n"""
1206 ''' <string>org.python-projects.eric-ide</string>\n''' 1299 """ <string>org.python-projects.eric-ide</string>\n"""
1207 ''' <key>NSRequiresAquaSystemAppearance</key>\n''' 1300 """ <key>NSRequiresAquaSystemAppearance</key>\n"""
1208 ''' <string>false</string>\n''' 1301 """ <string>false</string>\n"""
1209 ''' <key>LSEnvironment</key>\n''' 1302 """ <key>LSEnvironment</key>\n"""
1210 ''' <dict>\n''' 1303 """ <dict>\n"""
1211 ''' <key>LANG</key>\n''' 1304 """ <key>LANG</key>\n"""
1212 ''' <string>en_US.UTF-8</string>\n''' 1305 """ <string>en_US.UTF-8</string>\n"""
1213 ''' <key>LC_ALL</key>\n''' 1306 """ <key>LC_ALL</key>\n"""
1214 ''' <string>en_US.UTF-8</string>\n''' 1307 """ <string>en_US.UTF-8</string>\n"""
1215 ''' <key>LC_CTYPE</key>\n''' 1308 """ <key>LC_CTYPE</key>\n"""
1216 ''' <string>en_US.UTF-8</string>\n''' 1309 """ <string>en_US.UTF-8</string>\n"""
1217 ''' </dict>\n''' 1310 """ </dict>\n"""
1218 '''</dict>\n''' 1311 """</dict>\n"""
1219 '''</plist>\n'''.format( 1312 """</plist>\n""".format(
1220 macAppBundleName.replace(".app", ""), 1313 macAppBundleName.replace(".app", ""),
1221 Version.split(None, 1)[0], 1314 Version.split(None, 1)[0],
1222 CopyrightShort)) 1315 CopyrightShort,
1316 ),
1317 )
1223 1318
1224 1319
1225 def createInstallConfig(): 1320 def createInstallConfig():
1226 """ 1321 """
1227 Create the installation config dictionary. 1322 Create the installation config dictionary.
1228 """ 1323 """
1229 global modDir, platBinDir, cfg, apisDir, installApis 1324 global modDir, platBinDir, cfg, apisDir, installApis
1230 1325
1231 ericdir = os.path.join(modDir, "eric7") 1326 ericdir = os.path.join(modDir, "eric7")
1232 cfg = { 1327 cfg = {
1233 'ericDir': ericdir, 1328 "ericDir": ericdir,
1234 'ericPixDir': os.path.join(ericdir, "pixmaps"), 1329 "ericPixDir": os.path.join(ericdir, "pixmaps"),
1235 'ericIconDir': os.path.join(ericdir, "icons"), 1330 "ericIconDir": os.path.join(ericdir, "icons"),
1236 'ericDTDDir': os.path.join(ericdir, "DTDs"), 1331 "ericDTDDir": os.path.join(ericdir, "DTDs"),
1237 'ericCSSDir': os.path.join(ericdir, "CSSs"), 1332 "ericCSSDir": os.path.join(ericdir, "CSSs"),
1238 'ericStylesDir': os.path.join(ericdir, "Styles"), 1333 "ericStylesDir": os.path.join(ericdir, "Styles"),
1239 'ericThemesDir': os.path.join(ericdir, "Themes"), 1334 "ericThemesDir": os.path.join(ericdir, "Themes"),
1240 'ericDocDir': os.path.join(ericdir, "Documentation"), 1335 "ericDocDir": os.path.join(ericdir, "Documentation"),
1241 'ericExamplesDir': os.path.join(ericdir, "Examples"), 1336 "ericExamplesDir": os.path.join(ericdir, "Examples"),
1242 'ericTranslationsDir': os.path.join(ericdir, "i18n"), 1337 "ericTranslationsDir": os.path.join(ericdir, "i18n"),
1243 'ericTemplatesDir': os.path.join(ericdir, "DesignerTemplates"), 1338 "ericTemplatesDir": os.path.join(ericdir, "DesignerTemplates"),
1244 'ericCodeTemplatesDir': os.path.join(ericdir, 'CodeTemplates'), 1339 "ericCodeTemplatesDir": os.path.join(ericdir, "CodeTemplates"),
1245 'ericOthersDir': ericdir, 1340 "ericOthersDir": ericdir,
1246 'bindir': platBinDir, 1341 "bindir": platBinDir,
1247 'mdir': modDir, 1342 "mdir": modDir,
1248 } 1343 }
1249 if installApis: 1344 if installApis:
1250 if apisDir: 1345 if apisDir:
1251 cfg['apidir'] = apisDir 1346 cfg["apidir"] = apisDir
1252 else: 1347 else:
1253 cfg['apidir'] = os.path.join(ericdir, "api") 1348 cfg["apidir"] = os.path.join(ericdir, "api")
1254 else: 1349 else:
1255 cfg['apidir'] = "" 1350 cfg["apidir"] = ""
1351
1352
1256 configLength = 16 1353 configLength = 16
1257 1354
1258 1355
1259 def createConfig(): 1356 def createConfig():
1260 """ 1357 """
1261 Create a config file with the respective config entries. 1358 Create a config file with the respective config entries.
1262 """ 1359 """
1263 global cfg, macAppBundlePath, configName 1360 global cfg, macAppBundlePath, configName
1264 1361
1265 apis = [] 1362 apis = []
1266 if installApis: 1363 if installApis:
1267 for progLanguage in progLanguages: 1364 for progLanguage in progLanguages:
1268 for apiName in sorted( 1365 for apiName in sorted(
1269 glob.glob(os.path.join(eric7SourceDir, "APIs", progLanguage, 1366 glob.glob(os.path.join(eric7SourceDir, "APIs", progLanguage, "*.api"))
1270 "*.api"))): 1367 ):
1271 apis.append(os.path.basename(apiName)) 1368 apis.append(os.path.basename(apiName))
1272 if progLanguage == "Python": 1369 if progLanguage == "Python":
1273 # treat Python3 API files the same as Python API files 1370 # treat Python3 API files the same as Python API files
1274 for apiName in sorted( 1371 for apiName in sorted(
1275 glob.glob(os.path.join(eric7SourceDir, "APIs", "Python3", 1372 glob.glob(os.path.join(eric7SourceDir, "APIs", "Python3", "*.api"))
1276 "*.api"))): 1373 ):
1277 apis.append(os.path.basename(apiName)) 1374 apis.append(os.path.basename(apiName))
1278 1375
1279 # treat MicroPython API files the same as Python API files 1376 # treat MicroPython API files the same as Python API files
1280 for apiName in sorted( 1377 for apiName in sorted(
1281 glob.glob(os.path.join(eric7SourceDir, "APIs", 1378 glob.glob(
1282 "MicroPython", "*.api"))): 1379 os.path.join(eric7SourceDir, "APIs", "MicroPython", "*.api")
1380 )
1381 ):
1283 apis.append(os.path.basename(apiName)) 1382 apis.append(os.path.basename(apiName))
1284 1383
1285 macConfig = ( 1384 macConfig = (
1286 (""" 'macAppBundlePath': r'{0}',\n""" 1385 (
1287 """ 'macAppBundleName': r'{1}',\n""").format(macAppBundlePath, 1386 """ 'macAppBundlePath': r'{0}',\n"""
1288 macAppBundleName) 1387 """ 'macAppBundleName': r'{1}',\n"""
1289 if sys.platform == "darwin" else 1388 ).format(macAppBundlePath, macAppBundleName)
1290 "" 1389 if sys.platform == "darwin"
1390 else ""
1291 ) 1391 )
1292 config = ( 1392 config = (
1293 """# -*- coding: utf-8 -*-\n""" 1393 """# -*- coding: utf-8 -*-\n"""
1294 """#\n""" 1394 """#\n"""
1295 """# This module contains the configuration of the individual eric""" 1395 """# This module contains the configuration of the individual eric"""
1333 """\n""" 1433 """\n"""
1334 """ raise AttributeError(\n""" 1434 """ raise AttributeError(\n"""
1335 """ '"{{0}}" is not a valid configuration value'""" 1435 """ '"{{0}}" is not a valid configuration value'"""
1336 """.format(name))\n""" 1436 """.format(name))\n"""
1337 ).format( 1437 ).format(
1338 cfg['ericDir'], cfg['ericPixDir'], cfg['ericIconDir'], 1438 cfg["ericDir"],
1339 cfg['ericDTDDir'], cfg['ericCSSDir'], 1439 cfg["ericPixDir"],
1340 cfg['ericStylesDir'], cfg['ericThemesDir'], cfg['ericDocDir'], 1440 cfg["ericIconDir"],
1341 cfg['ericExamplesDir'], cfg['ericTranslationsDir'], 1441 cfg["ericDTDDir"],
1342 cfg['ericTemplatesDir'], 1442 cfg["ericCSSDir"],
1343 cfg['ericCodeTemplatesDir'], cfg['ericOthersDir'], 1443 cfg["ericStylesDir"],
1344 cfg['bindir'], cfg['mdir'], 1444 cfg["ericThemesDir"],
1345 cfg['apidir'], sorted(apis), 1445 cfg["ericDocDir"],
1446 cfg["ericExamplesDir"],
1447 cfg["ericTranslationsDir"],
1448 cfg["ericTemplatesDir"],
1449 cfg["ericCodeTemplatesDir"],
1450 cfg["ericOthersDir"],
1451 cfg["bindir"],
1452 cfg["mdir"],
1453 cfg["apidir"],
1454 sorted(apis),
1346 macConfig, 1455 macConfig,
1347 ) 1456 )
1348 copyToFile(configName, config) 1457 copyToFile(configName, config)
1349 1458
1350 1459
1351 def createInstallInfo(): 1460 def createInstallInfo():
1352 """ 1461 """
1353 Record information about the way eric was installed. 1462 Record information about the way eric was installed.
1354 """ 1463 """
1355 global createInstallInfoFile, installInfo, installCwd, cfg 1464 global createInstallInfoFile, installInfo, installCwd, cfg
1356 1465
1357 if createInstallInfoFile: 1466 if createInstallInfoFile:
1358 installDateTime = datetime.datetime.now(tz=None) 1467 installDateTime = datetime.datetime.now(tz=None)
1359 try: 1468 try:
1360 installInfo["sudo"] = os.getuid() == 0 1469 installInfo["sudo"] = os.getuid() == 0
1361 except AttributeError: 1470 except AttributeError:
1364 installInfo["exe"] = sys.executable 1473 installInfo["exe"] = sys.executable
1365 installInfo["argv"] = " ".join(shlex.quote(a) for a in sys.argv[:]) 1474 installInfo["argv"] = " ".join(shlex.quote(a) for a in sys.argv[:])
1366 installInfo["install_cwd"] = installCwd 1475 installInfo["install_cwd"] = installCwd
1367 installInfo["eric"] = cfg["ericDir"] 1476 installInfo["eric"] = cfg["ericDir"]
1368 installInfo["virtualenv"] = installInfo["eric"].startswith( 1477 installInfo["virtualenv"] = installInfo["eric"].startswith(
1369 os.path.expanduser("~")) 1478 os.path.expanduser("~")
1479 )
1370 installInfo["installed"] = True 1480 installInfo["installed"] = True
1371 installInfo["installed_on"] = installDateTime.strftime( 1481 installInfo["installed_on"] = installDateTime.strftime("%Y-%m-%d %H:%M:%S")
1372 "%Y-%m-%d %H:%M:%S")
1373 installInfo["guessed"] = False 1482 installInfo["guessed"] = False
1374 installInfo["edited"] = False 1483 installInfo["edited"] = False
1375 installInfo["pip"] = False 1484 installInfo["pip"] = False
1376 installInfo["remarks"] = "" 1485 installInfo["remarks"] = ""
1377 installInfo["install_cwd_edited"] = False 1486 installInfo["install_cwd_edited"] = False
1381 1490
1382 1491
1383 def pipInstall(packageName, message, force=True): 1492 def pipInstall(packageName, message, force=True):
1384 """ 1493 """
1385 Install the given package via pip. 1494 Install the given package via pip.
1386 1495
1387 @param packageName name of the package to be installed 1496 @param packageName name of the package to be installed
1388 @type str 1497 @type str
1389 @param message message to be shown to the user 1498 @param message message to be shown to the user
1390 @type str 1499 @type str
1391 @param force flag indicating to perform the installation 1500 @param force flag indicating to perform the installation
1393 @type bool 1502 @type bool
1394 @return flag indicating a successful installation 1503 @return flag indicating a successful installation
1395 @rtype bool 1504 @rtype bool
1396 """ 1505 """
1397 global yes2All 1506 global yes2All
1398 1507
1399 ok = False 1508 ok = False
1400 if yes2All or force: 1509 if yes2All or force:
1401 answer = "y" 1510 answer = "y"
1402 else: 1511 else:
1403 print("{0}\nShall '{1}' be installed using pip? (Y/n)" 1512 print(
1404 .format(message, packageName), end=" ") 1513 "{0}\nShall '{1}' be installed using pip? (Y/n)".format(
1405 answer = input() # secok 1514 message, packageName
1515 ),
1516 end=" ",
1517 )
1518 answer = input() # secok
1406 if answer in ("", "Y", "y"): 1519 if answer in ("", "Y", "y"):
1407 exitCode = subprocess.run( # secok 1520 exitCode = subprocess.run( # secok
1408 [sys.executable, "-m", "pip", "install", "--prefer-binary", 1521 [
1409 "--upgrade", packageName] 1522 sys.executable,
1523 "-m",
1524 "pip",
1525 "install",
1526 "--prefer-binary",
1527 "--upgrade",
1528 packageName,
1529 ]
1410 ).returncode 1530 ).returncode
1411 ok = (exitCode == 0) 1531 ok = exitCode == 0
1412 1532
1413 return ok 1533 return ok
1414 1534
1415 1535
1416 def isPipOutdated(): 1536 def isPipOutdated():
1417 """ 1537 """
1418 Check, if pip is outdated. 1538 Check, if pip is outdated.
1419 1539
1420 @return flag indicating an outdated pip 1540 @return flag indicating an outdated pip
1421 @rtype bool 1541 @rtype bool
1422 """ 1542 """
1423 try: 1543 try:
1424 pipOut = subprocess.run( # secok 1544 pipOut = subprocess.run( # secok
1425 [sys.executable, "-m", "pip", "list", "--outdated", 1545 [sys.executable, "-m", "pip", "list", "--outdated", "--format=json"],
1426 "--format=json"], 1546 check=True,
1427 check=True, capture_output=True, text=True 1547 capture_output=True,
1428 ).stdout 1548 text=True,
1549 ).stdout.strip().splitlines()[0]
1550 # only the first line contains the JSON data
1429 except (OSError, subprocess.CalledProcessError): 1551 except (OSError, subprocess.CalledProcessError):
1430 pipOut = "[]" # default empty list 1552 pipOut = "[]" # default empty list
1431 try: 1553 try:
1432 jsonList = json.loads(pipOut) 1554 jsonList = json.loads(pipOut)
1433 except Exception: 1555 except Exception:
1434 jsonList = [] 1556 jsonList = []
1435 for package in jsonList: 1557 for package in jsonList:
1436 if isinstance(package, dict) and package["name"] == "pip": 1558 if isinstance(package, dict) and package["name"] == "pip":
1437 print("'pip' is outdated (installed {0}, available {1})".format( 1559 print(
1438 package["version"], package["latest_version"] 1560 "'pip' is outdated (installed {0}, available {1})".format(
1439 )) 1561 package["version"], package["latest_version"]
1562 )
1563 )
1440 return True 1564 return True
1441 1565
1442 return False 1566 return False
1443 1567
1444 1568
1445 def updatePip(): 1569 def updatePip():
1446 """ 1570 """
1447 Update the installed pip package. 1571 Update the installed pip package.
1448 """ 1572 """
1449 global yes2All 1573 global yes2All
1450 1574
1451 if yes2All: 1575 if yes2All:
1452 answer = "y" 1576 answer = "y"
1453 else: 1577 else:
1454 print("Shall 'pip' be updated (recommended)? (Y/n)", end=" ") 1578 print("Shall 'pip' be updated (recommended)? (Y/n)", end=" ")
1455 answer = input() # secok 1579 answer = input() # secok
1456 if answer in ("", "Y", "y"): 1580 if answer in ("", "Y", "y"):
1457 subprocess.run( # secok 1581 subprocess.run( # secok
1458 [sys.executable, "-m", "pip", "install", "--upgrade", "pip"]) 1582 [sys.executable, "-m", "pip", "install", "--upgrade", "pip"]
1583 )
1459 1584
1460 1585
1461 def versionToStr(version): 1586 def versionToStr(version):
1462 """ 1587 """
1463 Function to convert a version number into a version string. 1588 Function to convert a version number into a version string.
1464 1589
1465 @param version version number to convert 1590 @param version version number to convert
1466 @type int 1591 @type int
1467 @return version string 1592 @return version string
1468 @rtype str 1593 @rtype str
1469 """ 1594 """
1470 parts = [] 1595 parts = []
1471 while version: 1596 while version:
1472 parts.append(version & 0xff) 1597 parts.append(version & 0xFF)
1473 version >>= 8 1598 version >>= 8
1474 return '.'.join(str(p) for p in reversed(parts)) 1599 return ".".join(str(p) for p in reversed(parts))
1475 1600
1476 1601
1477 def doDependancyChecks(): 1602 def doDependancyChecks():
1478 """ 1603 """
1479 Perform some dependency checks. 1604 Perform some dependency checks.
1480 """ 1605 """
1481 global verbose 1606 global verbose
1482 1607
1483 requiredVersions = { 1608 requiredVersions = {
1484 "pyqt6": 0x60200, # v6.2.0 1609 "pyqt6": 0x60200, # v6.2.0
1485 "pyqt6-charts": 0x60200, # v6.2.0 1610 "pyqt6-charts": 0x60200, # v6.2.0
1486 "pyqt6-webengine": 0x60200, # v6.2.0 1611 "pyqt6-webengine": 0x60200, # v6.2.0
1487 "pyqt6-qscintilla": 0x20d00, # v2.13.0 1612 "pyqt6-qscintilla": 0x20D00, # v2.13.0
1488 "sip": 0x60100, # v6.1.0 1613 "sip": 0x60100, # v6.1.0
1489 } 1614 }
1490 1615
1491 try: 1616 try:
1492 isSudo = os.getuid() == 0 and sys.platform != "darwin" 1617 isSudo = os.getuid() == 0 and sys.platform != "darwin"
1493 # disregard sudo installs on macOS 1618 # disregard sudo installs on macOS
1494 except AttributeError: 1619 except AttributeError:
1495 isSudo = False 1620 isSudo = False
1496 1621
1497 print('Checking dependencies') 1622 print("Checking dependencies")
1498 1623
1499 # update pip first even if we don't need to install anything 1624 # update pip first even if we don't need to install anything
1500 if not isSudo and isPipOutdated(): 1625 if not isSudo and isPipOutdated():
1501 updatePip() 1626 updatePip()
1502 print("\n") 1627 print("\n")
1503 1628
1504 # perform dependency checks 1629 # perform dependency checks
1505 if sys.version_info < (3, 7, 0) or sys.version_info >= (3, 12, 0): 1630 if sys.version_info < (3, 7, 0) or sys.version_info >= (3, 12, 0):
1506 print('Sorry, you must have Python 3.7.0 or higher, but less 3.12.0.') 1631 print("Sorry, you must have Python 3.7.0 or higher, but less 3.12.0.")
1507 print("Yours is {0}.".format( 1632 print("Yours is {0}.".format(".".join(str(v) for v in sys.version_info[:3])))
1508 ".".join(str(v) for v in sys.version_info[:3])
1509 ))
1510 exit(5) 1633 exit(5)
1511 1634
1512 try: 1635 try:
1513 import xml.etree # __IGNORE_WARNING__ 1636 import xml.etree # __IGNORE_WARNING__
1514 except ImportError: 1637 except ImportError:
1515 print('Your Python installation is missing the XML module.') 1638 print("Your Python installation is missing the XML module.")
1516 print('Please install it and try again.') 1639 print("Please install it and try again.")
1517 exit(5) 1640 exit(5)
1518 1641
1519 try: 1642 try:
1520 from PyQt6.QtCore import qVersion 1643 from PyQt6.QtCore import qVersion
1521 except ImportError as err: 1644 except ImportError as err:
1522 msg = "'PyQt6' could not be detected.{0}".format( 1645 msg = "'PyQt6' could not be detected.{0}".format(
1523 "\nError: {0}".format(err) if verbose else "") 1646 "\nError: {0}".format(err) if verbose else ""
1647 )
1524 installed = not isSudo and pipInstall( 1648 installed = not isSudo and pipInstall(
1525 "PyQt6>={0}".format(versionToStr(requiredVersions["pyqt6"])), 1649 "PyQt6>={0}".format(versionToStr(requiredVersions["pyqt6"])), msg
1526 msg
1527 ) 1650 )
1528 if installed: 1651 if installed:
1529 # try to import it again 1652 # try to import it again
1530 try: 1653 try:
1531 from PyQt6.QtCore import qVersion 1654 from PyQt6.QtCore import qVersion
1532 except ImportError as msg: 1655 except ImportError as msg:
1533 print('Sorry, please install PyQt6.') 1656 print("Sorry, please install PyQt6.")
1534 print('Error: {0}'.format(msg)) 1657 print("Error: {0}".format(msg))
1535 exit(1) 1658 exit(1)
1536 else: 1659 else:
1537 print('Sorry, please install PyQt6.') 1660 print("Sorry, please install PyQt6.")
1538 print('Error: {0}'.format(msg)) 1661 print("Error: {0}".format(msg))
1539 exit(1) 1662 exit(1)
1540 print("Found PyQt6") 1663 print("Found PyQt6")
1541 1664
1542 try: 1665 try:
1543 pyuic = "pyuic6" 1666 pyuic = "pyuic6"
1544 from PyQt6 import uic # __IGNORE_WARNING__ 1667 from PyQt6 import uic # __IGNORE_WARNING__
1545 except ImportError as err: 1668 except ImportError as err:
1546 print("Sorry, {0} is not installed.".format(pyuic)) 1669 print("Sorry, {0} is not installed.".format(pyuic))
1547 if verbose: 1670 if verbose:
1548 print('Error: {0}'.format(err)) 1671 print("Error: {0}".format(err))
1549 exit(1) 1672 exit(1)
1550 print("Found {0}".format(pyuic)) 1673 print("Found {0}".format(pyuic))
1551 1674
1552 try: 1675 try:
1553 from PyQt6 import QtWebEngineWidgets # __IGNORE_WARNING__ 1676 from PyQt6 import QtWebEngineWidgets # __IGNORE_WARNING__
1554 except ImportError as err: 1677 except ImportError as err:
1555 if isSudo: 1678 if isSudo:
1556 print("Optional 'PyQt6-WebEngine' could not be detected.") 1679 print("Optional 'PyQt6-WebEngine' could not be detected.")
1557 else: 1680 else:
1558 msg = ( 1681 msg = "Optional 'PyQt6-WebEngine' could not be detected.{0}".format(
1559 "Optional 'PyQt6-WebEngine' could not be detected.{0}" 1682 "\nError: {0}".format(err) if verbose else ""
1560 .format("\nError: {0}".format(err) if verbose else "")
1561 ) 1683 )
1562 pipInstall( 1684 pipInstall(
1563 "PyQt6-WebEngine>={0}".format( 1685 "PyQt6-WebEngine>={0}".format(
1564 versionToStr(requiredVersions["pyqt6-webengine"])), 1686 versionToStr(requiredVersions["pyqt6-webengine"])
1565 msg 1687 ),
1688 msg,
1566 ) 1689 )
1567 1690
1568 try: 1691 try:
1569 from PyQt6 import QtCharts # __IGNORE_WARNING__ 1692 from PyQt6 import QtCharts # __IGNORE_WARNING__
1570 except ImportError as err: 1693 except ImportError as err:
1571 if isSudo: 1694 if isSudo:
1572 print("Optional 'PyQt6-Charts' could not be detected.") 1695 print("Optional 'PyQt6-Charts' could not be detected.")
1573 else: 1696 else:
1574 msg = "Optional 'PyQt6-Charts' could not be detected.{0}".format( 1697 msg = "Optional 'PyQt6-Charts' could not be detected.{0}".format(
1575 "\nError: {0}".format(err) if verbose else "") 1698 "\nError: {0}".format(err) if verbose else ""
1699 )
1576 pipInstall( 1700 pipInstall(
1577 "PyQt6-Charts>={0}".format( 1701 "PyQt6-Charts>={0}".format(
1578 versionToStr(requiredVersions["pyqt6-charts"])), 1702 versionToStr(requiredVersions["pyqt6-charts"])
1579 msg 1703 ),
1704 msg,
1580 ) 1705 )
1581 print("Found PyQt6-Charts") 1706 print("Found PyQt6-Charts")
1582 1707
1583 try: 1708 try:
1584 from PyQt6 import Qsci # __IGNORE_WARNING__ 1709 from PyQt6 import Qsci # __IGNORE_WARNING__
1585 except ImportError as err: 1710 except ImportError as err:
1586 msg = "'PyQt6-QScintilla' could not be detected.{0}".format( 1711 msg = "'PyQt6-QScintilla' could not be detected.{0}".format(
1587 "\nError: {0}".format(err) if verbose else "") 1712 "\nError: {0}".format(err) if verbose else ""
1713 )
1588 installed = not isSudo and pipInstall( 1714 installed = not isSudo and pipInstall(
1589 "PyQt6-QScintilla>={0}".format( 1715 "PyQt6-QScintilla>={0}".format(
1590 versionToStr(requiredVersions["pyqt6-qscintilla"])), 1716 versionToStr(requiredVersions["pyqt6-qscintilla"])
1591 msg 1717 ),
1718 msg,
1592 ) 1719 )
1593 if installed: 1720 if installed:
1594 # try to import it again 1721 # try to import it again
1595 try: 1722 try:
1596 from PyQt6 import Qsci # __IGNORE_WARNING__ 1723 from PyQt6 import Qsci # __IGNORE_WARNING__
1724
1597 message = None 1725 message = None
1598 except ImportError as msg: 1726 except ImportError as msg:
1599 message = str(msg) 1727 message = str(msg)
1600 else: 1728 else:
1601 message = "PyQt6-QScintilla could not be installed." 1729 message = "PyQt6-QScintilla could not be installed."
1602 if message: 1730 if message:
1603 print("Sorry, please install QScintilla2 and") 1731 print("Sorry, please install QScintilla2 and")
1604 print("its PyQt6 wrapper.") 1732 print("its PyQt6 wrapper.")
1605 print('Error: {0}'.format(message)) 1733 print("Error: {0}".format(message))
1606 exit(1) 1734 exit(1)
1607 print("Found PyQt6-QScintilla") 1735 print("Found PyQt6-QScintilla")
1608 1736
1609 impModulesList = [ 1737 impModulesList = [
1610 "PyQt6.QtGui", "PyQt6.QtNetwork", "PyQt6.QtPrintSupport", 1738 "PyQt6.QtGui",
1611 "PyQt6.QtSql", "PyQt6.QtSvg", "PyQt6.QtSvgWidgets", "PyQt6.QtWidgets", 1739 "PyQt6.QtNetwork",
1740 "PyQt6.QtPrintSupport",
1741 "PyQt6.QtSql",
1742 "PyQt6.QtSvg",
1743 "PyQt6.QtSvgWidgets",
1744 "PyQt6.QtWidgets",
1612 ] 1745 ]
1613 optionalModulesList = { 1746 optionalModulesList = {
1614 # key is pip project name 1747 # key is pip project name
1615 # value is tuple of package name, pip install constraint 1748 # value is tuple of package name, pip install constraint
1616 "docutils": ("docutils", ""), 1749 "docutils": ("docutils", ""),
1625 "pyenchant": ("enchant", ""), 1758 "pyenchant": ("enchant", ""),
1626 "wheel": ("wheel", ""), 1759 "wheel": ("wheel", ""),
1627 "parso": ("parso", ""), 1760 "parso": ("parso", ""),
1628 "jedi": ("jedi", ""), 1761 "jedi": ("jedi", ""),
1629 "packaging": ("packaging", ""), 1762 "packaging": ("packaging", ""),
1630 "pipdeptree": ("pipdeptree", ""),
1631 "cyclonedx-python-lib": ("cyclonedx", ""), 1763 "cyclonedx-python-lib": ("cyclonedx", ""),
1632 "cyclonedx-bom": ("cyclonedx_py", ""), 1764 "cyclonedx-bom": ("cyclonedx_py", ""),
1633 "trove-classifiers": ("trove_classifiers", ""), 1765 "trove-classifiers": ("trove_classifiers", ""),
1766 "black": ("black", ">=22.6.0"),
1634 } 1767 }
1635 if not ignorePyqt6Tools: 1768 if not ignorePyqt6Tools:
1636 optionalModulesList["qt6-applications"] = ("qt6_applications", "") 1769 optionalModulesList["qt6-applications"] = ("qt6_applications", "")
1637 1770
1638 # check mandatory modules 1771 # check mandatory modules
1639 modulesOK = True 1772 modulesOK = True
1640 for impModule in impModulesList: 1773 for impModule in impModulesList:
1641 name = impModule.split(".")[1] 1774 name = impModule.split(".")[1]
1642 try: 1775 try:
1643 __import__(impModule) 1776 __import__(impModule)
1644 print("Found", name) 1777 print("Found", name)
1645 except ImportError as err: 1778 except ImportError as err:
1646 print('Sorry, please install {0}.'.format(name)) 1779 print("Sorry, please install {0}.".format(name))
1647 if verbose: 1780 if verbose:
1648 print('Error: {0}'.format(err)) 1781 print("Error: {0}".format(err))
1649 modulesOK = False 1782 modulesOK = False
1650 if not modulesOK: 1783 if not modulesOK:
1651 exit(1) 1784 exit(1)
1652 1785
1653 # check optional modules 1786 # check optional modules
1654 for optPackage in optionalModulesList: 1787 for optPackage in optionalModulesList:
1655 try: 1788 try:
1656 __import__(optionalModulesList[optPackage][0]) 1789 __import__(optionalModulesList[optPackage][0])
1657 print("Found", optPackage) 1790 print("Found", optPackage)
1658 except ImportError as err: 1791 except ImportError as err:
1659 if isSudo: 1792 if isSudo:
1660 print("Optional '{0}' could not be detected." 1793 print("Optional '{0}' could not be detected.".format(optPackage))
1661 .format(optPackage))
1662 else: 1794 else:
1663 msg = "Optional '{0}' could not be detected.{1}".format( 1795 msg = "Optional '{0}' could not be detected.{1}".format(
1664 optPackage, 1796 optPackage, "\nError: {0}".format(err) if verbose else ""
1665 "\nError: {0}".format(err) if verbose else ""
1666 ) 1797 )
1667 pipInstall( 1798 pipInstall(optPackage + optionalModulesList[optPackage][1], msg)
1668 optPackage + optionalModulesList[optPackage][1], 1799
1669 msg
1670 )
1671
1672 # determine the platform dependent black list 1800 # determine the platform dependent black list
1673 if sys.platform.startswith(("win", "cygwin")): 1801 if sys.platform.startswith(("win", "cygwin")):
1674 PlatformBlackLists = PlatformsBlackLists["windows"] 1802 PlatformBlackLists = PlatformsBlackLists["windows"]
1675 elif sys.platform.startswith("linux"): 1803 elif sys.platform.startswith("linux"):
1676 PlatformBlackLists = PlatformsBlackLists["linux"] 1804 PlatformBlackLists = PlatformsBlackLists["linux"]
1677 else: 1805 else:
1678 PlatformBlackLists = PlatformsBlackLists["mac"] 1806 PlatformBlackLists = PlatformsBlackLists["mac"]
1679 1807
1680 print("\nVersion Information") 1808 print("\nVersion Information")
1681 print("-------------------") 1809 print("-------------------")
1682 1810
1683 print("Python: {0:d}.{1:d}.{2:d}".format(*sys.version_info[:3])) 1811 print("Python: {0:d}.{1:d}.{2:d}".format(*sys.version_info[:3]))
1684 1812
1685 # check version of Qt 1813 # check version of Qt
1686 # =================== 1814 # ===================
1687 qtMajor = int(qVersion().split('.')[0]) 1815 qtMajor = int(qVersion().split(".")[0])
1688 qtMinor = int(qVersion().split('.')[1]) 1816 qtMinor = int(qVersion().split(".")[1])
1689 print("Qt6: {0}".format(qVersion().strip())) 1817 print("Qt6: {0}".format(qVersion().strip()))
1690 if qtMajor == 6 and qtMinor < 1: 1818 if qtMajor == 6 and qtMinor < 1:
1691 print('Sorry, you must have Qt version 6.1.0 or better.') 1819 print("Sorry, you must have Qt version 6.1.0 or better.")
1692 exit(2) 1820 exit(2)
1693 1821
1694 # check version of sip 1822 # check version of sip
1695 # ==================== 1823 # ====================
1696 with contextlib.suppress(ImportError, AttributeError): 1824 with contextlib.suppress(ImportError, AttributeError):
1697 try: 1825 try:
1698 from PyQt6 import sip 1826 from PyQt6 import sip
1699 except ImportError: 1827 except ImportError:
1700 import sip 1828 import sip
1701 print("sip:", sip.SIP_VERSION_STR.strip()) 1829 print("sip:", sip.SIP_VERSION_STR.strip())
1702 # always assume, that snapshots or dev versions are new enough 1830 # always assume, that snapshots or dev versions are new enough
1703 if ( 1831 if "snapshot" not in sip.SIP_VERSION_STR and "dev" not in sip.SIP_VERSION_STR:
1704 "snapshot" not in sip.SIP_VERSION_STR and
1705 "dev" not in sip.SIP_VERSION_STR
1706 ):
1707 if sip.SIP_VERSION < requiredVersions["sip"]: 1832 if sip.SIP_VERSION < requiredVersions["sip"]:
1708 print( 1833 print(
1709 'Sorry, you must have sip {0} or higher or' 1834 "Sorry, you must have sip {0} or higher or"
1710 ' a recent development release.' 1835 " a recent development release.".format(
1711 .format(versionToStr(requiredVersions["sip"])) 1836 versionToStr(requiredVersions["sip"])
1837 )
1712 ) 1838 )
1713 exit(3) 1839 exit(3)
1714 # check for blacklisted versions 1840 # check for blacklisted versions
1715 for vers in BlackLists["sip"] + PlatformBlackLists["sip"]: 1841 for vers in BlackLists["sip"] + PlatformBlackLists["sip"]:
1716 if vers == sip.SIP_VERSION: 1842 if vers == sip.SIP_VERSION:
1717 print( 1843 print(
1718 'Sorry, sip version {0} is not compatible with eric.' 1844 "Sorry, sip version {0} is not compatible with eric.".format(
1719 .format(versionToStr(vers))) 1845 versionToStr(vers)
1720 print('Please install another version.') 1846 )
1847 )
1848 print("Please install another version.")
1721 exit(3) 1849 exit(3)
1722 1850
1723 # check version of PyQt6 1851 # check version of PyQt6
1724 # ====================== 1852 # ======================
1725 from PyQt6.QtCore import PYQT_VERSION, PYQT_VERSION_STR 1853 from PyQt6.QtCore import PYQT_VERSION, PYQT_VERSION_STR
1854
1726 print("PyQt6:", PYQT_VERSION_STR.strip()) 1855 print("PyQt6:", PYQT_VERSION_STR.strip())
1727 # always assume, that snapshots or dev versions are new enough 1856 # always assume, that snapshots or dev versions are new enough
1728 if "snapshot" not in PYQT_VERSION_STR and "dev" not in PYQT_VERSION_STR: 1857 if "snapshot" not in PYQT_VERSION_STR and "dev" not in PYQT_VERSION_STR:
1729 if PYQT_VERSION < requiredVersions["pyqt6"]: 1858 if PYQT_VERSION < requiredVersions["pyqt6"]:
1730 print( 1859 print(
1731 'Sorry, you must have PyQt {0} or better or' 1860 "Sorry, you must have PyQt {0} or better or"
1732 ' a recent development release.' 1861 " a recent development release.".format(
1733 .format(versionToStr(requiredVersions["pyqt6"])) 1862 versionToStr(requiredVersions["pyqt6"])
1863 )
1734 ) 1864 )
1735 exit(4) 1865 exit(4)
1736 # check for blacklisted versions 1866 # check for blacklisted versions
1737 for vers in BlackLists["PyQt6"] + PlatformBlackLists["PyQt6"]: 1867 for vers in BlackLists["PyQt6"] + PlatformBlackLists["PyQt6"]:
1738 if vers == PYQT_VERSION: 1868 if vers == PYQT_VERSION:
1739 print('Sorry, PyQt version {0} is not compatible with eric.' 1869 print(
1740 .format(versionToStr(vers))) 1870 "Sorry, PyQt version {0} is not compatible with eric.".format(
1741 print('Please install another version.') 1871 versionToStr(vers)
1872 )
1873 )
1874 print("Please install another version.")
1742 exit(4) 1875 exit(4)
1743 1876
1744 # check version of QScintilla 1877 # check version of QScintilla
1745 # =========================== 1878 # ===========================
1746 from PyQt6.Qsci import QSCINTILLA_VERSION, QSCINTILLA_VERSION_STR 1879 from PyQt6.Qsci import QSCINTILLA_VERSION, QSCINTILLA_VERSION_STR
1880
1747 print("PyQt6-QScintilla:", QSCINTILLA_VERSION_STR.strip()) 1881 print("PyQt6-QScintilla:", QSCINTILLA_VERSION_STR.strip())
1748 # always assume, that snapshots or dev versions are new enough 1882 # always assume, that snapshots or dev versions are new enough
1749 if ( 1883 if "snapshot" not in QSCINTILLA_VERSION_STR and "dev" not in QSCINTILLA_VERSION_STR:
1750 "snapshot" not in QSCINTILLA_VERSION_STR and
1751 "dev" not in QSCINTILLA_VERSION_STR
1752 ):
1753 if QSCINTILLA_VERSION < requiredVersions["pyqt6-qscintilla"]: 1884 if QSCINTILLA_VERSION < requiredVersions["pyqt6-qscintilla"]:
1754 print( 1885 print(
1755 'Sorry, you must have PyQt6-QScintilla {0} or higher or' 1886 "Sorry, you must have PyQt6-QScintilla {0} or higher or"
1756 ' a recent development release.' 1887 " a recent development release.".format(
1757 .format(versionToStr(requiredVersions["pyqt6-qscintilla"])) 1888 versionToStr(requiredVersions["pyqt6-qscintilla"])
1889 )
1758 ) 1890 )
1759 exit(5) 1891 exit(5)
1760 # check for blacklisted versions 1892 # check for blacklisted versions
1761 for vers in ( 1893 for vers in BlackLists["QScintilla2"] + PlatformBlackLists["QScintilla2"]:
1762 BlackLists["QScintilla2"] +
1763 PlatformBlackLists["QScintilla2"]
1764 ):
1765 if vers == QSCINTILLA_VERSION: 1894 if vers == QSCINTILLA_VERSION:
1766 print( 1895 print(
1767 'Sorry, QScintilla2 version {0} is not compatible with' 1896 "Sorry, QScintilla2 version {0} is not compatible with"
1768 ' eric.'.format(versionToStr(vers))) 1897 " eric.".format(versionToStr(vers))
1769 print('Please install another version.') 1898 )
1899 print("Please install another version.")
1770 exit(5) 1900 exit(5)
1771 1901
1772 # print version info for additional modules 1902 # print version info for additional modules
1773 with contextlib.suppress(NameError, AttributeError): 1903 with contextlib.suppress(NameError, AttributeError):
1774 print("PyQt6-Charts:", QtCharts.PYQT_CHART_VERSION_STR) 1904 print("PyQt6-Charts:", QtCharts.PYQT_CHART_VERSION_STR)
1775 1905
1776 with contextlib.suppress(ImportError, AttributeError): 1906 with contextlib.suppress(ImportError, AttributeError):
1777 from PyQt6 import QtWebEngineCore 1907 from PyQt6 import QtWebEngineCore
1908
1778 print("PyQt6-WebEngine:", QtWebEngineCore.PYQT_WEBENGINE_VERSION_STR) 1909 print("PyQt6-WebEngine:", QtWebEngineCore.PYQT_WEBENGINE_VERSION_STR)
1779 1910
1780 print() 1911 print()
1781 print("All dependencies ok.") 1912 print("All dependencies ok.")
1782 print() 1913 print()
1783 1914
1784 1915
1785 def __pyName(py_dir, py_file): 1916 def __pyName(py_dir, py_file):
1786 """ 1917 """
1787 Local function to create the Python source file name for the compiled 1918 Local function to create the Python source file name for the compiled
1788 .ui file. 1919 .ui file.
1789 1920
1790 @param py_dir suggested name of the directory (string) 1921 @param py_dir suggested name of the directory (string)
1791 @param py_file suggested name for the compile source file (string) 1922 @param py_file suggested name for the compile source file (string)
1792 @return tuple of directory name (string) and source file name (string) 1923 @return tuple of directory name (string) and source file name (string)
1793 """ 1924 """
1794 return py_dir, "Ui_{0}".format(py_file) 1925 return py_dir, "Ui_{0}".format(py_file)
1797 def compileUiFiles(): 1928 def compileUiFiles():
1798 """ 1929 """
1799 Compile the .ui files to Python sources. 1930 Compile the .ui files to Python sources.
1800 """ 1931 """
1801 from PyQt6.uic import compileUiDir 1932 from PyQt6.uic import compileUiDir
1933
1802 compileUiDir(eric7SourceDir, True, __pyName) 1934 compileUiDir(eric7SourceDir, True, __pyName)
1803 1935
1804 1936
1805 def prepareInfoFile(fileName): 1937 def prepareInfoFile(fileName):
1806 """ 1938 """
1807 Function to prepare an Info.py file when installing from source. 1939 Function to prepare an Info.py file when installing from source.
1808 1940
1809 @param fileName name of the Python file containing the info (string) 1941 @param fileName name of the Python file containing the info (string)
1810 """ 1942 """
1811 if not fileName: 1943 if not fileName:
1812 return 1944 return
1813 1945
1814 with contextlib.suppress(OSError): 1946 with contextlib.suppress(OSError):
1815 os.rename(fileName, fileName + ".orig") 1947 os.rename(fileName, fileName + ".orig")
1816 localHg = ( 1948 localHg = (
1817 os.path.join(sys.exec_prefix, "Scripts", "hg.exe") 1949 os.path.join(sys.exec_prefix, "Scripts", "hg.exe")
1818 if sys.platform.startswith(("win", "cygwin")) else 1950 if sys.platform.startswith(("win", "cygwin"))
1819 os.path.join(sys.exec_prefix, "bin", "hg") 1951 else os.path.join(sys.exec_prefix, "bin", "hg")
1820 ) 1952 )
1821 for hg in (localHg, "hg"): 1953 for hg in (localHg, "hg"):
1822 with contextlib.suppress(OSError, subprocess.CalledProcessError): 1954 with contextlib.suppress(OSError, subprocess.CalledProcessError):
1823 hgOut = subprocess.run( # secok 1955 hgOut = subprocess.run( # secok
1824 [hg, "identify", "-i"], check=True, 1956 [hg, "identify", "-i"], check=True, capture_output=True, text=True
1825 capture_output=True, text=True
1826 ).stdout 1957 ).stdout
1827 if hgOut: 1958 if hgOut:
1828 break 1959 break
1829 else: 1960 else:
1830 hgOut = "" 1961 hgOut = ""
1832 hgOut = hgOut.strip() 1963 hgOut = hgOut.strip()
1833 if hgOut.endswith("+"): 1964 if hgOut.endswith("+"):
1834 hgOut = hgOut[:-1] 1965 hgOut = hgOut[:-1]
1835 with open(fileName + ".orig", "r", encoding="utf-8") as f: 1966 with open(fileName + ".orig", "r", encoding="utf-8") as f:
1836 text = f.read() 1967 text = f.read()
1837 text = ( 1968 text = text.replace("@@REVISION@@", hgOut).replace(
1838 text.replace("@@REVISION@@", hgOut) 1969 "@@VERSION@@", "rev_" + hgOut
1839 .replace("@@VERSION@@", "rev_" + hgOut)
1840 ) 1970 )
1841 copyToFile(fileName, text) 1971 copyToFile(fileName, text)
1842 else: 1972 else:
1843 shutil.copy(fileName + ".orig", fileName) 1973 shutil.copy(fileName + ".orig", fileName)
1844 1974
1845 1975
1846 def getWinregEntry(name, path): 1976 def getWinregEntry(name, path):
1847 """ 1977 """
1848 Function to get an entry from the Windows Registry. 1978 Function to get an entry from the Windows Registry.
1849 1979
1850 @param name variable name 1980 @param name variable name
1851 @type str 1981 @type str
1852 @param path registry path of the variable 1982 @param path registry path of the variable
1853 @type str 1983 @type str
1854 @return value of requested registry variable 1984 @return value of requested registry variable
1856 """ 1986 """
1857 try: 1987 try:
1858 import winreg 1988 import winreg
1859 except ImportError: 1989 except ImportError:
1860 return None 1990 return None
1861 1991
1862 try: 1992 try:
1863 registryKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, 1993 registryKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, winreg.KEY_READ)
1864 winreg.KEY_READ)
1865 value, _ = winreg.QueryValueEx(registryKey, name) 1994 value, _ = winreg.QueryValueEx(registryKey, name)
1866 winreg.CloseKey(registryKey) 1995 winreg.CloseKey(registryKey)
1867 return value 1996 return value
1868 except WindowsError: 1997 except WindowsError:
1869 return None 1998 return None
1870 1999
1871 2000
1872 def createWindowsShortcut(linkPath, targetPath, iconPath): 2001 def createWindowsShortcut(linkPath, targetPath, iconPath):
1873 """ 2002 """
1874 Create Windows shortcut. 2003 Create Windows shortcut.
1875 2004
1876 @param linkPath path of the shortcut file 2005 @param linkPath path of the shortcut file
1877 @type str 2006 @type str
1878 @param targetPath path the shortcut shall point to 2007 @param targetPath path the shortcut shall point to
1879 @type str 2008 @type str
1880 @param iconPath path of the icon file 2009 @param iconPath path of the icon file
1881 @type str 2010 @type str
1882 """ 2011 """
1883 from win32com.client import Dispatch 2012 from win32com.client import Dispatch
1884 from pywintypes import com_error 2013 from pywintypes import com_error
1885 2014
1886 with contextlib.suppress(com_error): 2015 with contextlib.suppress(com_error):
1887 shell = Dispatch('WScript.Shell') 2016 shell = Dispatch("WScript.Shell")
1888 shortcut = shell.CreateShortCut(linkPath) 2017 shortcut = shell.CreateShortCut(linkPath)
1889 shortcut.Targetpath = targetPath 2018 shortcut.Targetpath = targetPath
1890 shortcut.WorkingDirectory = os.path.dirname(targetPath) 2019 shortcut.WorkingDirectory = os.path.dirname(targetPath)
1891 shortcut.IconLocation = iconPath 2020 shortcut.IconLocation = iconPath
1892 shortcut.save() 2021 shortcut.save()
1893 2022
1894 2023
1895 def windowsDesktopNames(): 2024 def windowsDesktopNames():
1896 """ 2025 """
1897 Function to generate the link names for the Windows Desktop. 2026 Function to generate the link names for the Windows Desktop.
1898 2027
1899 @return list of desktop link names 2028 @return list of desktop link names
1900 @rtype list of str 2029 @rtype list of str
1901 """ 2030 """
1902 return [e[0] for e in windowsDesktopEntries()] 2031 return [e[0] for e in windowsDesktopEntries()]
1903 2032
1904 2033
1905 def windowsDesktopEntries(): 2034 def windowsDesktopEntries():
1906 """ 2035 """
1907 Function to generate data for the Windows Desktop links. 2036 Function to generate data for the Windows Desktop links.
1908 2037
1909 @return list of tuples containing the desktop link name, 2038 @return list of tuples containing the desktop link name,
1910 the link target and the icon target 2039 the link target and the icon target
1911 @rtype list of tuples of (str, str, str) 2040 @rtype list of tuples of (str, str, str)
1912 """ 2041 """
1913 global cfg 2042 global cfg
1914 2043
1915 majorVersion, minorVersion = sys.version_info[:2] 2044 majorVersion, minorVersion = sys.version_info[:2]
1916 entriesTemplates = [ 2045 entriesTemplates = [
1917 ("eric7 (Python {0}.{1}).lnk", 2046 (
1918 os.path.join(cfg["bindir"], "eric7.cmd"), 2047 "eric7 (Python {0}.{1}).lnk",
1919 os.path.join(cfg["ericPixDir"], "eric7.ico")), 2048 os.path.join(cfg["bindir"], "eric7.cmd"),
1920 ("eric7 Browser (Python {0}.{1}).lnk", 2049 os.path.join(cfg["ericPixDir"], "eric7.ico"),
1921 os.path.join(cfg["bindir"], "eric7_browser.cmd"), 2050 ),
1922 os.path.join(cfg["ericPixDir"], "ericWeb48.ico")), 2051 (
2052 "eric7 Browser (Python {0}.{1}).lnk",
2053 os.path.join(cfg["bindir"], "eric7_browser.cmd"),
2054 os.path.join(cfg["ericPixDir"], "ericWeb48.ico"),
2055 ),
1923 ] 2056 ]
1924 2057
1925 return [ 2058 return [
1926 (e[0].format(majorVersion, minorVersion), e[1], e[2]) 2059 (e[0].format(majorVersion, minorVersion), e[1], e[2]) for e in entriesTemplates
1927 for e in entriesTemplates
1928 ] 2060 ]
1929 2061
1930 2062
1931 def windowsProgramsEntry(): 2063 def windowsProgramsEntry():
1932 """ 2064 """
1933 Function to generate the name of the Start Menu top entry. 2065 Function to generate the name of the Start Menu top entry.
1934 2066
1935 @return name of the Start Menu top entry 2067 @return name of the Start Menu top entry
1936 @rtype str 2068 @rtype str
1937 """ 2069 """
1938 majorVersion, minorVersion = sys.version_info[:2] 2070 majorVersion, minorVersion = sys.version_info[:2]
1939 return "eric7 (Python {0}.{1})".format(majorVersion, minorVersion) 2071 return "eric7 (Python {0}.{1})".format(majorVersion, minorVersion)
1954 global macAppBundlePath, macAppBundleName, macPythonExe 2086 global macAppBundlePath, macAppBundleName, macPythonExe
1955 global installApis, doCleanDesktopLinks, yes2All 2087 global installApis, doCleanDesktopLinks, yes2All
1956 global createInstallInfoFile, installCwd 2088 global createInstallInfoFile, installCwd
1957 global ignorePyqt6Tools 2089 global ignorePyqt6Tools
1958 global verbose 2090 global verbose
1959 2091
1960 if sys.version_info < (3, 7, 0) or sys.version_info > (3, 99, 99): 2092 if sys.version_info < (3, 7, 0) or sys.version_info > (3, 99, 99):
1961 print('Sorry, eric requires at least Python 3.7 for running.') 2093 print("Sorry, eric requires at least Python 3.7 for running.")
1962 exit(5) 2094 exit(5)
1963 2095
1964 progName = os.path.basename(argv[0]) 2096 progName = os.path.basename(argv[0])
1965 2097
1966 installCwd = os.getcwd() 2098 installCwd = os.getcwd()
1967 2099
1968 if os.path.dirname(argv[0]): 2100 if os.path.dirname(argv[0]):
1969 os.chdir(os.path.dirname(argv[0])) 2101 os.chdir(os.path.dirname(argv[0]))
1970 2102
1971 initGlobals() 2103 initGlobals()
1972 2104
1973 try: 2105 try:
1974 if sys.platform.startswith(("win", "cygwin")): 2106 if sys.platform.startswith(("win", "cygwin")):
1975 optlist, args = getopt.getopt( 2107 optlist, args = getopt.getopt(
1976 argv[1:], "chvxza:b:d:f:", 2108 argv[1:],
1977 ["help", "no-apis", "no-info", "no-tools", "verbose", "yes"]) 2109 "chvxza:b:d:f:",
2110 ["help", "no-apis", "no-info", "no-tools", "verbose", "yes"],
2111 )
1978 elif sys.platform == "darwin": 2112 elif sys.platform == "darwin":
1979 optlist, args = getopt.getopt( 2113 optlist, args = getopt.getopt(
1980 argv[1:], "chvxza:b:d:f:i:m:n:p:", 2114 argv[1:],
1981 ["help", "no-apis", "no-info", "no-tools", "verbose", "yes"]) 2115 "chvxza:b:d:f:i:m:n:p:",
2116 ["help", "no-apis", "no-info", "no-tools", "verbose", "yes"],
2117 )
1982 else: 2118 else:
1983 optlist, args = getopt.getopt( 2119 optlist, args = getopt.getopt(
1984 argv[1:], "chvxza:b:d:f:i:", 2120 argv[1:],
1985 ["help", "no-apis", "no-info", "no-tools", "verbose", "yes"]) 2121 "chvxza:b:d:f:i:",
2122 ["help", "no-apis", "no-info", "no-tools", "verbose", "yes"],
2123 )
1986 except getopt.GetoptError as err: 2124 except getopt.GetoptError as err:
1987 print(err) 2125 print(err)
1988 usage() 2126 usage()
1989 2127
1990 global platBinDir 2128 global platBinDir
1991 2129
1992 depChecks = True 2130 depChecks = True
1993 2131
1994 for opt, arg in optlist: 2132 for opt, arg in optlist:
1995 if opt in ["-h", "--help"]: 2133 if opt in ["-h", "--help"]:
1996 usage(0) 2134 usage(0)
2009 elif opt == "-z": 2147 elif opt == "-z":
2010 doCompile = False 2148 doCompile = False
2011 elif opt == "-f": 2149 elif opt == "-f":
2012 with open(arg) as f: 2150 with open(arg) as f:
2013 try: 2151 try:
2014 exec(compile(f.read(), arg, 'exec'), globals()) 2152 exec(compile(f.read(), arg, "exec"), globals())
2015 # secok 2153 # secok
2016 if len(cfg) != configLength: 2154 if len(cfg) != configLength:
2017 print("The configuration dictionary in '{0}' is" 2155 print(
2018 " incorrect. Aborting".format(arg)) 2156 "The configuration dictionary in '{0}' is"
2157 " incorrect. Aborting".format(arg)
2158 )
2019 exit(6) 2159 exit(6)
2020 except Exception: 2160 except Exception:
2021 cfg = {} 2161 cfg = {}
2022 elif opt == "-m": 2162 elif opt == "-m":
2023 macAppBundleName = arg 2163 macAppBundleName = arg
2035 ignorePyqt6Tools = True 2175 ignorePyqt6Tools = True
2036 elif opt == "--no-info": 2176 elif opt == "--no-info":
2037 createInstallInfoFile = False 2177 createInstallInfoFile = False
2038 elif opt in ["-v", "--verbose"]: 2178 elif opt in ["-v", "--verbose"]:
2039 verbose = True 2179 verbose = True
2040 2180
2041 infoName = "" 2181 infoName = ""
2042 installFromSource = not os.path.isdir(sourceDir) 2182 installFromSource = not os.path.isdir(sourceDir)
2043 2183
2044 # check dependencies 2184 # check dependencies
2045 if depChecks: 2185 if depChecks:
2046 doDependancyChecks() 2186 doDependancyChecks()
2047 2187
2188 if installFromSource:
2189 sourceDir = os.path.abspath("..")
2190
2191 eric7SourceDir = (
2192 os.path.join(sourceDir, "src", "eric7")
2193 if os.path.exists(os.path.join(sourceDir, "src", "eric7"))
2194 else os.path.join(sourceDir, "eric7")
2195 )
2196
2048 # cleanup source if installing from source 2197 # cleanup source if installing from source
2049 if installFromSource: 2198 if installFromSource:
2050 print("Cleaning up source ...") 2199 print("Cleaning up source ...")
2051 sourceDir = os.path.abspath("..") 2200 cleanupSource(sourceDir)
2052 eric7SourceDir = os.path.join(sourceDir, "eric7")
2053 cleanupSource(eric7SourceDir)
2054 print() 2201 print()
2055 2202
2056 if installFromSource:
2057 sourceDir = os.path.abspath("..")
2058 eric7SourceDir = os.path.join(sourceDir, "eric7")
2059 configName = os.path.join(eric7SourceDir, "eric7config.py") 2203 configName = os.path.join(eric7SourceDir, "eric7config.py")
2060 if os.path.exists(os.path.join(sourceDir, ".hg")): 2204 if os.path.exists(os.path.join(sourceDir, ".hg")):
2061 # we are installing from source with repo 2205 # we are installing from source with repo
2062 infoName = os.path.join(eric7SourceDir, "UI", "Info.py") 2206 infoName = os.path.join(eric7SourceDir, "UI", "Info.py")
2063 prepareInfoFile(infoName) 2207 prepareInfoFile(infoName)
2064 2208
2065 if len(cfg) == 0: 2209 if len(cfg) == 0:
2066 createInstallConfig() 2210 createInstallConfig()
2067 2211
2068 # get rid of development config file, if it exists 2212 # get rid of development config file, if it exists
2069 with contextlib.suppress(OSError): 2213 with contextlib.suppress(OSError):
2070 if installFromSource: 2214 if installFromSource:
2071 os.rename(configName, configName + ".orig") 2215 os.rename(configName, configName + ".orig")
2072 configNameC = configName + 'c' 2216 configNameC = configName + "c"
2073 if os.path.exists(configNameC): 2217 if os.path.exists(configNameC):
2074 os.remove(configNameC) 2218 os.remove(configNameC)
2075 os.remove(configName) 2219 os.remove(configName)
2076 2220
2077 # cleanup old installation 2221 # cleanup old installation
2078 print("Cleaning up old installation ...") 2222 print("Cleaning up old installation ...")
2079 try: 2223 try:
2080 if doCleanup: 2224 if doCleanup:
2081 if distDir: 2225 if distDir:
2082 shutil.rmtree(distDir, True) 2226 shutil.rmtree(distDir, True)
2083 else: 2227 else:
2084 cleanUp() 2228 cleanUp()
2085 except OSError as msg: 2229 except OSError as msg:
2086 sys.stderr.write('Error: {0}\nTry install as root.\n'.format(msg)) 2230 sys.stderr.write("Error: {0}\nTry install as root.\n".format(msg))
2087 exit(7) 2231 exit(7)
2088 2232
2089 # Create a config file and delete the default one 2233 # Create a config file and delete the default one
2090 print("\nCreating configuration file ...") 2234 print("\nCreating configuration file ...")
2091 createConfig() 2235 createConfig()
2092 2236
2093 createInstallInfo() 2237 createInstallInfo()
2094 2238
2095 # Compile .ui files 2239 # Compile .ui files
2096 print("\nCompiling user interface files ...") 2240 print("\nCompiling user interface files ...")
2097 # step 1: remove old Ui_*.py files 2241 # step 1: remove old Ui_*.py files
2098 for root, _, files in os.walk(sourceDir): 2242 for root, _, files in os.walk(sourceDir):
2099 for file in [f for f in files if fnmatch.fnmatch(f, 'Ui_*.py')]: 2243 for file in [f for f in files if fnmatch.fnmatch(f, "Ui_*.py")]:
2100 os.remove(os.path.join(root, file)) 2244 os.remove(os.path.join(root, file))
2101 # step 2: compile the forms 2245 # step 2: compile the forms
2102 compileUiFiles() 2246 compileUiFiles()
2103 2247
2104 if doCompile: 2248 if doCompile:
2105 print("\nCompiling source files ...") 2249 print("\nCompiling source files ...")
2106 skipRe = re.compile(r"DebugClients[\\/]Python[\\/]") 2250 skipRe = re.compile(r"DebugClients[\\/]Python[\\/]")
2107 sys.stdout = io.StringIO() 2251 sys.stdout = io.StringIO()
2108 if distDir: 2252 if distDir:
2109 compileall.compile_dir( 2253 compileall.compile_dir(
2110 eric7SourceDir, 2254 eric7SourceDir,
2111 ddir=os.path.join(distDir, modDir, cfg['ericDir']), 2255 ddir=os.path.join(distDir, modDir, cfg["ericDir"]),
2112 rx=skipRe, 2256 rx=skipRe,
2113 quiet=True) 2257 quiet=True,
2258 )
2114 py_compile.compile( 2259 py_compile.compile(
2115 configName, 2260 configName, dfile=os.path.join(distDir, modDir, "eric7config.py")
2116 dfile=os.path.join(distDir, modDir, "eric7config.py")) 2261 )
2117 else: 2262 else:
2118 compileall.compile_dir( 2263 compileall.compile_dir(
2119 eric7SourceDir, 2264 eric7SourceDir,
2120 ddir=os.path.join(modDir, cfg['ericDir']), 2265 ddir=os.path.join(modDir, cfg["ericDir"]),
2121 rx=skipRe, 2266 rx=skipRe,
2122 quiet=True) 2267 quiet=True,
2123 py_compile.compile(configName, 2268 )
2124 dfile=os.path.join(modDir, "eric7config.py")) 2269 py_compile.compile(configName, dfile=os.path.join(modDir, "eric7config.py"))
2125 sys.stdout = sys.__stdout__ 2270 sys.stdout = sys.__stdout__
2126 print("\nInstalling eric ...") 2271 print("\nInstalling eric ...")
2127 res = installEric() 2272 res = installEric()
2128 2273
2129 if createInstallInfoFile: 2274 if createInstallInfoFile:
2130 with open(os.path.join(cfg["ericDir"], 2275 with open(
2131 installInfoName), "w") as installInfoFile: 2276 os.path.join(cfg["ericDir"], installInfoName), "w"
2277 ) as installInfoFile:
2132 json.dump(installInfo, installInfoFile, indent=2) 2278 json.dump(installInfo, installInfoFile, indent=2)
2133 2279
2134 # do some cleanup 2280 # do some cleanup
2135 with contextlib.suppress(OSError): 2281 with contextlib.suppress(OSError):
2136 if installFromSource: 2282 if installFromSource:
2137 os.remove(configName) 2283 os.remove(configName)
2138 configNameC = configName + 'c' 2284 configNameC = configName + "c"
2139 if os.path.exists(configNameC): 2285 if os.path.exists(configNameC):
2140 os.remove(configNameC) 2286 os.remove(configNameC)
2141 os.rename(configName + ".orig", configName) 2287 os.rename(configName + ".orig", configName)
2142 with contextlib.suppress(OSError): 2288 with contextlib.suppress(OSError):
2143 if installFromSource and infoName: 2289 if installFromSource and infoName:
2144 os.remove(infoName) 2290 os.remove(infoName)
2145 infoNameC = infoName + 'c' 2291 infoNameC = infoName + "c"
2146 if os.path.exists(infoNameC): 2292 if os.path.exists(infoNameC):
2147 os.remove(infoNameC) 2293 os.remove(infoNameC)
2148 os.rename(infoName + ".orig", infoName) 2294 os.rename(infoName + ".orig", infoName)
2149 2295
2150 print("\nInstallation complete.") 2296 print("\nInstallation complete.")
2151 print() 2297 print()
2152 2298
2153 exit(res) 2299 exit(res)
2154 2300
2155 2301
2156 if __name__ == "__main__": 2302 if __name__ == "__main__":
2157 try: 2303 try:
2158 main(sys.argv) 2304 main(sys.argv)
2159 except SystemExit: 2305 except SystemExit:
2160 raise 2306 raise
2161 except Exception: 2307 except Exception:
2162 print("""An internal error occured. Please report all the output""" 2308 print(
2163 """ of the program,\nincluding the following traceback, to""" 2309 """An internal error occured. Please report all the output"""
2164 """ eric-bugs@eric-ide.python-projects.org.\n""") 2310 """ of the program,\nincluding the following traceback, to"""
2311 """ eric-bugs@eric-ide.python-projects.org.\n"""
2312 )
2165 raise 2313 raise
2166 2314
2167 # 2315 #
2168 # eflag: noqa = M801 2316 # eflag: noqa = M801

eric ide

mercurial