|
1 #!/usr/bin/env python3 |
|
2 # -*- coding: utf-8 -*- |
|
3 |
|
4 # Copyright (c) 2002 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
5 # |
|
6 # This is the uninstall script for eric6. |
|
7 # |
|
8 |
|
9 """ |
|
10 Uninstallation script for the eric6 IDE and all eric6 related tools. |
|
11 """ |
|
12 |
|
13 from __future__ import unicode_literals, print_function |
|
14 |
|
15 import sys |
|
16 import os |
|
17 import shutil |
|
18 import glob |
|
19 import distutils.sysconfig |
|
20 |
|
21 if sys.version_info[0] == 2: |
|
22 try: |
|
23 from PyQt5 import sip |
|
24 except ImportError: |
|
25 import sip |
|
26 sip.setapi('QString', 2) |
|
27 else: |
|
28 raw_input = input |
|
29 |
|
30 # get a local eric6config.py out of the way |
|
31 if os.path.exists("eric6config.py"): |
|
32 os.rename("eric6config.py", "eric6config.py.orig") |
|
33 from eric6config import getConfig |
|
34 |
|
35 # Define the globals. |
|
36 progName = None |
|
37 currDir = os.getcwd() |
|
38 pyModDir = None |
|
39 progLanguages = ["Python", "Ruby", "QSS"] |
|
40 includePythonVariant = False |
|
41 defaultMacAppBundleName = "eric6.app" |
|
42 defaultMacAppBundlePath = "/Applications" |
|
43 settingsNameOrganization = "Eric6" |
|
44 settingsNameGlobal = "eric6" |
|
45 |
|
46 # Define file name markers for Python variants |
|
47 PythonMarkers = { |
|
48 2: "_py2", |
|
49 3: "_py3", |
|
50 } |
|
51 |
|
52 |
|
53 def exit(rcode=0): |
|
54 """ |
|
55 Exit the uninstall script. |
|
56 |
|
57 @param rcode result code to report back (integer) |
|
58 """ |
|
59 global currDir |
|
60 |
|
61 # restore the local eric6config.py |
|
62 if os.path.exists("eric6config.py.orig"): |
|
63 if os.path.exists("eric6config.py"): |
|
64 os.remove("eric6config.py") |
|
65 os.rename("eric6config.py.orig", "eric6config.py") |
|
66 |
|
67 if sys.platform.startswith(("win", "cygwin")): |
|
68 # different meaning of input between Py2 and Py3 |
|
69 try: |
|
70 input("Press enter to continue...") |
|
71 except (EOFError, SyntaxError): |
|
72 pass |
|
73 |
|
74 os.chdir(currDir) |
|
75 |
|
76 sys.exit(rcode) |
|
77 |
|
78 |
|
79 def usage(rcode=2): |
|
80 """ |
|
81 Display a usage message and exit. |
|
82 |
|
83 @param rcode return code passed back to the calling process (integer) |
|
84 """ |
|
85 global progName |
|
86 |
|
87 print("Usage:") |
|
88 print(" {0} [-h]".format(progName)) |
|
89 print("where:") |
|
90 print(" -h display this help message") |
|
91 print(" -y remove executables with Python variant in name") |
|
92 |
|
93 exit(rcode) |
|
94 |
|
95 |
|
96 def initGlobals(): |
|
97 """ |
|
98 Set the values of globals that need more than a simple assignment. |
|
99 """ |
|
100 global pyModDir |
|
101 |
|
102 pyModDir = distutils.sysconfig.get_python_lib(True) |
|
103 |
|
104 |
|
105 def wrapperNames(dname, wfile): |
|
106 """ |
|
107 Create the platform specific names for the wrapper script. |
|
108 |
|
109 @param dname name of the directory to place the wrapper into |
|
110 @param wfile basename (without extension) of the wrapper script |
|
111 @return the names of the wrapper scripts |
|
112 """ |
|
113 if sys.platform.startswith(("win", "cygwin")): |
|
114 wnames = (dname + "\\" + wfile + ".cmd", |
|
115 dname + "\\" + wfile + ".bat") |
|
116 else: |
|
117 wnames = (dname + "/" + wfile, ) |
|
118 |
|
119 return wnames |
|
120 |
|
121 |
|
122 def uninstallEric(): |
|
123 """ |
|
124 Uninstall the eric files. |
|
125 """ |
|
126 global pyModDir |
|
127 |
|
128 # Remove the menu entry for Linux systems |
|
129 if sys.platform.startswith("linux"): |
|
130 uninstallLinuxSpecifics() |
|
131 # Remove the Desktop and Start Menu entries for Windows systems |
|
132 elif sys.platform.startswith(("win", "cygwin")): |
|
133 uninstallWindowsLinks() |
|
134 |
|
135 # Remove the wrapper scripts |
|
136 rem_wnames = [ |
|
137 "eric6_api", "eric6_compare", |
|
138 "eric6_configure", "eric6_diff", |
|
139 "eric6_doc", "eric6_qregularexpression", |
|
140 "eric6_qregexp", "eric6_re", |
|
141 "eric6_trpreviewer", "eric6_uipreviewer", |
|
142 "eric6_unittest", "eric6", |
|
143 "eric6_tray", "eric6_editor", |
|
144 "eric6_plugininstall", "eric6_pluginuninstall", |
|
145 "eric6_pluginrepository", "eric6_sqlbrowser", |
|
146 "eric6_webbrowser", "eric6_iconeditor", |
|
147 "eric6_snap", "eric6_hexeditor", "eric6_browser", |
|
148 "eric6_shell", |
|
149 ] |
|
150 if includePythonVariant: |
|
151 marker = PythonMarkers[sys.version_info.major] |
|
152 rem_wnames = [n + marker for n in rem_wnames] |
|
153 |
|
154 try: |
|
155 for rem_wname in rem_wnames: |
|
156 for rwname in wrapperNames(getConfig('bindir'), rem_wname): |
|
157 if os.path.exists(rwname): |
|
158 os.remove(rwname) |
|
159 |
|
160 # Cleanup our config file(s) |
|
161 for name in ['eric6config.py', 'eric6config.pyc', 'eric6.pth']: |
|
162 e5cfile = os.path.join(pyModDir, name) |
|
163 if os.path.exists(e5cfile): |
|
164 os.remove(e5cfile) |
|
165 e5cfile = os.path.join(pyModDir, "__pycache__", name) |
|
166 path, ext = os.path.splitext(e5cfile) |
|
167 for f in glob.glob("{0}.*{1}".format(path, ext)): |
|
168 os.remove(f) |
|
169 |
|
170 # Cleanup the install directories |
|
171 for name in ['ericExamplesDir', 'ericDocDir', 'ericDTDDir', |
|
172 'ericCSSDir', 'ericIconDir', 'ericPixDir', |
|
173 'ericTemplatesDir', 'ericCodeTemplatesDir', |
|
174 'ericOthersDir', 'ericStylesDir', 'ericDir']: |
|
175 dirpath = getConfig(name) |
|
176 if os.path.exists(dirpath): |
|
177 shutil.rmtree(dirpath, True) |
|
178 |
|
179 # Cleanup translations |
|
180 for name in glob.glob( |
|
181 os.path.join(getConfig('ericTranslationsDir'), 'eric6_*.qm')): |
|
182 if os.path.exists(name): |
|
183 os.remove(name) |
|
184 |
|
185 # Cleanup API files |
|
186 apidir = getConfig('apidir') |
|
187 if apidir: |
|
188 for progLanguage in progLanguages: |
|
189 for name in getConfig('apis'): |
|
190 apiname = os.path.join(apidir, progLanguage.lower(), name) |
|
191 if os.path.exists(apiname): |
|
192 os.remove(apiname) |
|
193 for apiname in glob.glob( |
|
194 os.path.join(apidir, progLanguage.lower(), "*.bas")): |
|
195 if os.path.basename(apiname) != "eric6.bas": |
|
196 os.remove(apiname) |
|
197 |
|
198 if sys.platform == "darwin": |
|
199 # delete the Mac app bundle |
|
200 uninstallMacAppBundle() |
|
201 |
|
202 # remove plug-in directories |
|
203 removePluginDirectories() |
|
204 |
|
205 # remove the eric data directory |
|
206 removeDataDirectory() |
|
207 |
|
208 # remove the eric configuration directory |
|
209 removeConfigurationData() |
|
210 |
|
211 print("\nUninstallation completed") |
|
212 except (IOError, OSError) as msg: |
|
213 sys.stderr.write( |
|
214 'Error: {0}\nTry uninstall with admin rights.\n'.format(msg)) |
|
215 exit(7) |
|
216 |
|
217 |
|
218 def uninstallWindowsLinks(): |
|
219 """ |
|
220 Clean up the Desktop and Start Menu entries for Windows. |
|
221 """ |
|
222 try: |
|
223 from pywintypes import com_error # __IGNORE_WARNING__ |
|
224 except ImportError: |
|
225 # links were not created by install.py |
|
226 return |
|
227 |
|
228 regPath = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer" + \ |
|
229 "\\User Shell Folders" |
|
230 |
|
231 # 1. cleanup desktop links |
|
232 regName = "Desktop" |
|
233 desktopEntry = getWinregEntry(regName, regPath) |
|
234 if desktopEntry: |
|
235 desktopFolder = os.path.normpath(os.path.expandvars(desktopEntry)) |
|
236 for linkName in windowsDesktopNames(): |
|
237 linkPath = os.path.join(desktopFolder, linkName) |
|
238 if os.path.exists(linkPath): |
|
239 try: |
|
240 os.remove(linkPath) |
|
241 except EnvironmentError: |
|
242 # maybe restrictions prohibited link removal |
|
243 print("Could not remove '{0}'.".format(linkPath)) |
|
244 |
|
245 # 2. cleanup start menu entry |
|
246 regName = "Programs" |
|
247 programsEntry = getWinregEntry(regName, regPath) |
|
248 if programsEntry: |
|
249 programsFolder = os.path.normpath(os.path.expandvars(programsEntry)) |
|
250 eric6EntryPath = os.path.join(programsFolder, windowsProgramsEntry()) |
|
251 if os.path.exists(eric6EntryPath): |
|
252 try: |
|
253 shutil.rmtree(eric6EntryPath) |
|
254 except EnvironmentError: |
|
255 # maybe restrictions prohibited link removal |
|
256 print("Could not remove '{0}'.".format(eric6EntryPath)) |
|
257 |
|
258 |
|
259 def uninstallLinuxSpecifics(): |
|
260 """ |
|
261 Uninstall Linux specific files. |
|
262 """ |
|
263 if os.getuid() == 0: |
|
264 for name in ["/usr/share/pixmaps/eric.png", |
|
265 "/usr/share/pixmaps/ericWeb.png"]: |
|
266 if os.path.exists(name): |
|
267 os.remove(name) |
|
268 if includePythonVariant: |
|
269 marker = PythonMarkers[sys.version_info.major] |
|
270 else: |
|
271 marker = "" |
|
272 for name in [ |
|
273 "/usr/share/applications/eric6" + marker + ".desktop", |
|
274 "/usr/share/appdata/eric6" + marker + ".appdata.xml", |
|
275 "/usr/share/metainfo/eric6" + marker + ".appdata.xml", |
|
276 "/usr/share/applications/eric6_webbrowser" + marker + |
|
277 ".desktop", |
|
278 "/usr/share/applications/eric6_browser" + marker + |
|
279 ".desktop", |
|
280 "/usr/share/pixmaps/eric" + marker + ".png", |
|
281 "/usr/share/pixmaps/ericWeb" + marker + ".png", |
|
282 ]: |
|
283 if os.path.exists(name): |
|
284 os.remove(name) |
|
285 elif os.getuid() >= 1000: |
|
286 # it is assumed that user ids start at 1000 |
|
287 for name in ["~/.local/share/pixmaps/eric.png", |
|
288 "~/.local/share/pixmaps/ericWeb.png"]: |
|
289 path = os.path.expanduser(name) |
|
290 if os.path.exists(path): |
|
291 os.remove(path) |
|
292 if includePythonVariant: |
|
293 marker = PythonMarkers[sys.version_info.major] |
|
294 else: |
|
295 marker = "" |
|
296 for name in [ |
|
297 "~/.local/share/applications/eric6" + marker + ".desktop", |
|
298 "~/.local/share/appdata/eric6" + marker + ".appdata.xml", |
|
299 "~/.local/share/metainfo/eric6" + marker + ".appdata.xml", |
|
300 "~/.local/share/applications/eric6_webbrowser" + marker + |
|
301 ".desktop", |
|
302 "~/.local/share/applications/eric6_browser" + marker + |
|
303 ".desktop", |
|
304 "~/.local/share/pixmaps/eric" + marker + ".png", |
|
305 "~/.local/share/pixmaps/ericWeb" + marker + ".png", |
|
306 ]: |
|
307 path = os.path.expanduser(name) |
|
308 if os.path.exists(path): |
|
309 os.remove(path) |
|
310 |
|
311 |
|
312 def uninstallMacAppBundle(): |
|
313 """ |
|
314 Uninstall the macOS application bundle. |
|
315 """ |
|
316 if os.path.exists("/Developer/Applications/Eric6"): |
|
317 shutil.rmtree("/Developer/Applications/Eric6") |
|
318 try: |
|
319 macAppBundlePath = getConfig("macAppBundlePath") |
|
320 macAppBundleName = getConfig("macAppBundleName") |
|
321 except AttributeError: |
|
322 macAppBundlePath = defaultMacAppBundlePath |
|
323 macAppBundleName = defaultMacAppBundleName |
|
324 for bundlePath in [os.path.join(defaultMacAppBundlePath, |
|
325 macAppBundleName), |
|
326 os.path.join(macAppBundlePath, |
|
327 macAppBundleName), |
|
328 ]: |
|
329 if os.path.exists(bundlePath): |
|
330 shutil.rmtree(bundlePath) |
|
331 |
|
332 |
|
333 def removePluginDirectories(): |
|
334 """ |
|
335 Remove the plug-in directories. |
|
336 """ |
|
337 pathsToRemove = [] |
|
338 |
|
339 globalPluginsDir = os.path.join(getConfig('mdir'), "eric6plugins") |
|
340 if os.path.exists(globalPluginsDir): |
|
341 pathsToRemove.append(globalPluginsDir) |
|
342 |
|
343 localPluginsDir = os.path.join(getConfigDir(), "eric6plugins") |
|
344 if os.path.exists(localPluginsDir): |
|
345 pathsToRemove.append(localPluginsDir) |
|
346 |
|
347 if pathsToRemove: |
|
348 print("Found these plug-in directories") |
|
349 for path in pathsToRemove: |
|
350 print(" - {0}".format(path)) |
|
351 answer = "c" |
|
352 while answer not in ["y", "Y", "n", "N", ""]: |
|
353 answer = raw_input( |
|
354 "Shall these directories be removed (y/N)? ") |
|
355 if answer in ["y", "Y"]: |
|
356 for path in pathsToRemove: |
|
357 shutil.rmtree(path) |
|
358 |
|
359 |
|
360 def removeDataDirectory(): |
|
361 """ |
|
362 Remove the eric data directory. |
|
363 """ |
|
364 cfg = getConfigDir() |
|
365 if os.path.exists(cfg): |
|
366 print("Found the eric data directory") |
|
367 print(" - {0}".format(cfg)) |
|
368 answer = "c" |
|
369 while answer not in ["y", "Y", "n", "N", ""]: |
|
370 answer = raw_input( |
|
371 "Shall this directory be removed (y/N)? ") |
|
372 if answer in ["y", "Y"]: |
|
373 shutil.rmtree(cfg) |
|
374 |
|
375 |
|
376 def removeConfigurationData(): |
|
377 """ |
|
378 Remove the eric configuration directory. |
|
379 """ |
|
380 try: |
|
381 from PyQt5.QtCore import QSettings |
|
382 except ImportError: |
|
383 try: |
|
384 from PyQt4.QtCore import QSettings |
|
385 except ImportError: |
|
386 print("No PyQt variant installed. The configuration directory") |
|
387 print("cannot be determined. You have to remove it manually.\n") |
|
388 return |
|
389 |
|
390 settings = QSettings(QSettings.IniFormat, QSettings.UserScope, |
|
391 settingsNameOrganization, settingsNameGlobal) |
|
392 settingsDir = os.path.dirname(settings.fileName()) |
|
393 if os.path.exists(settingsDir): |
|
394 print("Found the eric configuration directory") |
|
395 print(" - {0}".format(settingsDir)) |
|
396 answer = "c" |
|
397 while answer not in ["y", "Y", "n", "N", ""]: |
|
398 answer = raw_input( |
|
399 "Shall this directory be removed (y/N)? ") |
|
400 if answer in ["y", "Y"]: |
|
401 shutil.rmtree(settingsDir) |
|
402 |
|
403 |
|
404 def getConfigDir(): |
|
405 """ |
|
406 Module function to get the name of the directory storing the config data. |
|
407 |
|
408 @return directory name of the config dir (string) |
|
409 """ |
|
410 cdn = ".eric6" |
|
411 return os.path.join(os.path.expanduser("~"), cdn) |
|
412 |
|
413 |
|
414 def getWinregEntry(name, path): |
|
415 """ |
|
416 Function to get an entry from the Windows Registry. |
|
417 |
|
418 @param name variable name |
|
419 @type str |
|
420 @param path registry path of the variable |
|
421 @type str |
|
422 @return value of requested registry variable |
|
423 @rtype any |
|
424 """ |
|
425 # From http://stackoverflow.com/a/35286642 |
|
426 import winreg |
|
427 try: |
|
428 registryKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, |
|
429 winreg.KEY_READ) |
|
430 value, _ = winreg.QueryValueEx(registryKey, name) |
|
431 winreg.CloseKey(registryKey) |
|
432 return value |
|
433 except WindowsError: |
|
434 return None |
|
435 |
|
436 |
|
437 def windowsDesktopNames(): |
|
438 """ |
|
439 Function to generate the link names for the Windows Desktop. |
|
440 |
|
441 @return list of desktop link names |
|
442 @rtype list of str |
|
443 """ |
|
444 majorVersion, minorVersion = sys.version_info[:2] |
|
445 linkTemplates = [ |
|
446 "eric6 (Python {0}.{1}).lnk", |
|
447 "eric6 Browser (Python {0}.{1}).lnk", |
|
448 ] |
|
449 |
|
450 return [l.format(majorVersion, minorVersion) for l in linkTemplates] |
|
451 |
|
452 |
|
453 def windowsProgramsEntry(): |
|
454 """ |
|
455 Function to generate the name of the Start Menu top entry. |
|
456 |
|
457 @return name of the Start Menu top entry |
|
458 @rtype str |
|
459 """ |
|
460 majorVersion, minorVersion = sys.version_info[:2] |
|
461 return "eric6 (Python {0}.{1})".format(majorVersion, minorVersion) |
|
462 |
|
463 |
|
464 def main(argv): |
|
465 """ |
|
466 The main function of the script. |
|
467 |
|
468 @param argv list of command line arguments |
|
469 """ |
|
470 import getopt |
|
471 |
|
472 global includePythonVariant |
|
473 |
|
474 initGlobals() |
|
475 |
|
476 # Parse the command line. |
|
477 global progName |
|
478 progName = os.path.basename(argv[0]) |
|
479 |
|
480 try: |
|
481 optlist, args = getopt.getopt(argv[1:], "hy") |
|
482 except getopt.GetoptError: |
|
483 usage() |
|
484 |
|
485 global platBinDir |
|
486 |
|
487 for opt, _arg in optlist: |
|
488 if opt == "-h": |
|
489 usage(0) |
|
490 if opt == "-y": |
|
491 includePythonVariant = True |
|
492 |
|
493 print("\nUninstalling eric6 ...") |
|
494 uninstallEric() |
|
495 print("\nUninstallation complete.") |
|
496 print() |
|
497 |
|
498 exit(0) |
|
499 |
|
500 |
|
501 if __name__ == "__main__": |
|
502 try: |
|
503 main(sys.argv) |
|
504 except SystemExit: |
|
505 raise |
|
506 except Exception: |
|
507 print("""An internal error occured. Please report all the output of""" |
|
508 """ the program,\n""" |
|
509 """including the following traceback, to""" |
|
510 """ eric-bugs@eric-ide.python-projects.org.\n""") |
|
511 raise |
|
512 |
|
513 # |
|
514 # eflag: noqa = M801 |