scripts/uninstall.py

Sat, 06 Apr 2024 12:50:04 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 06 Apr 2024 12:50:04 +0200
branch
eric7
changeset 10667
a82709c26b19
parent 10518
1682f3203ae5
child 10694
f46c1e224e8a
child 10864
8917b1a45546
permissions
-rw-r--r--

Corrected the uninstallation script for Windows.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Copyright (c) 2002 - 2024 Detlev Offenbach <detlev@die-offenbachs.de>
#
# This is the uninstall script for eric.
#

"""
Uninstallation script for the eric IDE and all eric related tools.
"""

import argparse
import contextlib
import glob
import importlib.util
import os
import shutil
import sys
import sysconfig

# Define the globals.
currDir = os.getcwd()
pyModDir = None
progLanguages = ["MicroPython", "Python3", "QSS"]
defaultMacAppBundleName = "eric7.app"
defaultMacAppBundlePath = "/Applications"
defaultMacUserAppBundlePath = os.path.expanduser("~/Applications")
settingsNameOrganization = "Eric7"
settingsNameGlobal = "eric7"


def exit(rcode=0):
    """
    Exit the uninstall script.

    @param rcode result code to report back
    @type int
    """
    global currDir

    # restore the local eric7config.py
    if os.path.exists("eric7config.py.orig"):
        if os.path.exists("eric7config.py"):
            os.remove("eric7config.py")
        os.rename("eric7config.py.orig", "eric7config.py")

    if sys.platform.startswith(("win", "cygwin")):
        with contextlib.suppress(EOFError):
            input("Press enter to continue...")  # secok

    os.chdir(currDir)

    sys.exit(rcode)


# get a local eric7config.py out of the way
if os.path.exists("eric7config.py"):
    os.rename("eric7config.py", "eric7config.py.orig")
try:
    from eric7.Globals import getConfig
except ImportError:
    print("The eric IDE doesn't seem to be installed. Aborting.")
    exit(1)
except SyntaxError:
    # an incomplete or old config file was found
    print("The eric IDE seems to be installed incompletely. Aborting.")
    exit(1)


def initGlobals():
    """
    Set the values of globals that need more than a simple assignment.
    """
    global pyModDir

    pyModDir = sysconfig.get_path("platlib")
    if not os.access(pyModDir, os.W_OK):
        # can't write to the standard path, use the 'user' path instead
        if sys.platform.startswith(("win", "cygwin")):
            scheme = "nt_user"
        elif sys.platform == "darwin":
            scheme = "osx_framework_user"
        else:
            scheme = "posix_user"
        pyModDir = sysconfig.get_path("platlib", scheme)


def wrapperNames(dname, wfile):
    """
    Create the platform specific names for the wrapper script.

    @param dname name of the directory to place the wrapper into
    @type str
    @param wfile basename (without extension) of the wrapper script
    @type str
    @return list of names of the wrapper scripts
    @rtype list of str
    """
    wnames = (
        [dname + "\\" + wfile + ".cmd", dname + "\\" + wfile + ".bat"]
        if sys.platform.startswith(("win", "cygwin"))
        else [dname + "/" + wfile]
    )

    return wnames


def uninstallEric():
    """
    Uninstall the eric files.
    """
    global pyModDir

    # Remove the menu entry for Linux systems
    if sys.platform.startswith(("linux", "freebsd")):
        uninstallLinuxSpecifics()
    # Remove the Desktop and Start Menu entries for Windows systems
    elif sys.platform.startswith(("win", "cygwin")):
        uninstallWindowsLinks()

    # Remove the wrapper scripts
    rem_wnames = [
        "eric7_api",
        "eric7_browser",
        "eric7_compare",
        "eric7_configure",
        "eric7_diff",
        "eric7_doc",
        "eric7_editor",
        "eric7_hexeditor",
        "eric7_iconeditor",
        "eric7_ide",
        "eric7_mpy",
        "eric7_pdf",
        "eric7_pip",
        "eric7_plugininstall",
        "eric7_pluginrepository",
        "eric7_pluginuninstall",
        "eric7_qregularexpression",
        "eric7_re",
        "eric7_shell",
        "eric7_snap",
        "eric7_sqlbrowser",
        "eric7_testing",
        "eric7_tray",
        "eric7_trpreviewer",
        "eric7_uipreviewer",
        "eric7_virtualenv",
        # obsolete scripts below
        "eric7_unittest",
        "eric7",
    ]

    try:
        for rem_wname in rem_wnames:
            for rwname in wrapperNames(getConfig("bindir"), rem_wname):
                if os.path.exists(rwname):
                    os.remove(rwname)

        # Cleanup our config file(s)
        for name in ["eric7config.py", "eric7config.pyc", "eric7.pth"]:
            e5cfile = os.path.join(pyModDir, name)
            if os.path.exists(e5cfile):
                os.remove(e5cfile)
            e5cfile = os.path.join(pyModDir, "__pycache__", name)
            path, ext = os.path.splitext(e5cfile)
            for f in glob.glob("{0}.*{1}".format(path, ext)):
                os.remove(f)

        # Cleanup the install directories
        for name in [
            "ericExamplesDir",
            "ericDocDir",
            "ericDTDDir",
            "ericCSSDir",
            "ericIconDir",
            "ericPixDir",
            "ericTemplatesDir",
            "ericCodeTemplatesDir",
            "ericOthersDir",
            "ericStylesDir",
            "ericThemesDir",
            "ericDir",
        ]:
            with contextlib.suppress(AttributeError):
                dirpath = getConfig(name)
                if os.path.exists(dirpath):
                    shutil.rmtree(dirpath, ignore_errors=True)

        # Cleanup translations
        for name in glob.glob(
            os.path.join(getConfig("ericTranslationsDir"), "eric7_*.qm")
        ):
            if os.path.exists(name):
                os.remove(name)

        # Cleanup API files
        apidir = getConfig("apidir")
        if apidir:
            for progLanguage in progLanguages:
                for name in getConfig("apis"):
                    # step 1: programming language as given
                    apiname = os.path.join(apidir, progLanguage, name)
                    if os.path.exists(apiname):
                        os.remove(apiname)
                    # step 2: programming language as lowercase
                    apiname = os.path.join(apidir, progLanguage.lower(), name)
                    if os.path.exists(apiname):
                        os.remove(apiname)
                for apiname in glob.glob(
                    os.path.join(apidir, progLanguage, "*.bas")
                ) + glob.glob(os.path.join(apidir, progLanguage.lower(), "*.bas")):
                    if os.path.exists(apiname):
                        os.remove(apiname)

                # remove empty directories
                with contextlib.suppress(FileNotFoundError, OSError):
                    os.rmdir(os.path.join(apidir, progLanguage))
                with contextlib.suppress(FileNotFoundError, OSError):
                    os.rmdir(os.path.join(apidir, progLanguage.lower()))

        if sys.platform == "darwin":
            # delete the Mac app bundle
            uninstallMacAppBundle()

        # remove plug-in directories
        removePluginDirectories()

        # remove the eric data directory
        removeDataDirectory()

        # remove the eric configuration directory
        removeConfigurationData()

        print("\nUninstallation completed")
    except OSError as msg:
        sys.stderr.write("Error: {0}\nTry uninstall with admin rights.\n".format(msg))
        exit(7)


def uninstallWindowsLinks():
    """
    Clean up the Desktop and Start Menu entries for Windows.
    """
    if importlib.util.find_spec("pywintypes") is None:
        # links were not created by install.py
        return

    regPath = (
        "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders"
    )

    # 1. cleanup desktop links
    regName = "Desktop"
    desktopEntry = getWinregEntry(regName, regPath)
    if desktopEntry:
        desktopFolder = os.path.normpath(os.path.expandvars(desktopEntry))
        for linkName in windowsDesktopNames():
            linkPath = os.path.join(desktopFolder, linkName)
            if os.path.exists(linkPath):
                try:
                    os.remove(linkPath)
                except OSError:
                    # maybe restrictions prohibited link removal
                    print("Could not remove '{0}'.".format(linkPath))

    # 2. cleanup start menu entry
    regName = "Programs"
    programsEntry = getWinregEntry(regName, regPath)
    if programsEntry:
        programsFolder = os.path.normpath(os.path.expandvars(programsEntry))
        eric7EntryPath = os.path.join(programsFolder, windowsProgramsEntry())
        if os.path.exists(eric7EntryPath):
            try:
                shutil.rmtree(eric7EntryPath)
            except OSError:
                # maybe restrictions prohibited link removal
                print("Could not remove '{0}'.".format(eric7EntryPath))


def uninstallLinuxSpecifics():
    """
    Uninstall Linux specific files.
    """
    if os.getuid() == 0:
        for name in [
            "/usr/share/appdata/eric7.appdata.xml",
            "/usr/share/metainfo/eric7.appdata.xml",
            "/usr/share/applications/eric7_browser.desktop",
            "/usr/share/applications/eric7_ide.desktop",
            "/usr/share/applications/eric7_mpy.desktop",
            "/usr/share/icons/eric.png",
            "/usr/share/icons/ericMPy.png",
            "/usr/share/icons/ericWeb.png",
            "/usr/share/pixmaps/eric.png",
            "/usr/share/pixmaps/ericMPy.png",
            "/usr/share/pixmaps/ericWeb.png",
            # obsolete entries below
            "/usr/share/applications/eric7.desktop",
        ]:
            if os.path.exists(name):
                os.remove(name)
    elif os.getuid() >= 1000:
        # it is assumed that user ids start at 1000
        for name in [
            "~/.local/share/appdata/eric7.appdata.xml",
            "~/.local/share/metainfo/eric7.appdata.xml",
            "~/.local/share/applications/eric7_browser.desktop",
            "~/.local/share/applications/eric7_ide.desktop",
            "~/.local/share/applications/eric7_mpy.desktop",
            "~/.local/share/icons/eric.png",
            "~/.local/share/icons/ericMPy.png",
            "~/.local/share/icons/ericWeb.png",
            "~/.local/share/pixmaps/eric.png",
            "~/.local/share/pixmaps/ericMPy.png",
            "~/.local/share/pixmaps/ericWeb.png",
            # obsolete entries below
            "~/.local/share/applications/eric7.desktop",
        ]:
            path = os.path.expanduser(name)
            if os.path.exists(path):
                os.remove(path)


def uninstallMacAppBundle():
    """
    Uninstall the macOS application bundle.
    """
    if os.path.exists("/Developer/Applications/Eric7"):
        shutil.rmtree("/Developer/Applications/Eric7")
    try:
        macAppBundlePath = getConfig("macAppBundlePath")
        macAppBundleName = getConfig("macAppBundleName")
    except AttributeError:
        macAppBundlePath = (
            defaultMacAppBundlePath if os.getpid() == 0 else defaultMacUserAppBundlePath
        )
        macAppBundleName = defaultMacAppBundleName
    for bundlePath in [
        os.path.join(defaultMacAppBundlePath, macAppBundleName),
        os.path.join(defaultMacUserAppBundlePath, macAppBundleName),
        os.path.join(macAppBundlePath, macAppBundleName),
    ]:
        if os.path.exists(bundlePath):
            shutil.rmtree(bundlePath)


def removePluginDirectories():
    """
    Remove the plug-in directories.
    """
    pathsToRemove = []

    globalPluginsDir = os.path.join(getConfig("mdir"), "eric7plugins")
    if os.path.exists(globalPluginsDir):
        pathsToRemove.append(globalPluginsDir)

    localPluginsDir = os.path.join(getConfigDir(), "eric7plugins")
    if os.path.exists(localPluginsDir):
        pathsToRemove.append(localPluginsDir)

    if pathsToRemove:
        print("Found these plug-in directories")
        for path in pathsToRemove:
            print("  - {0}".format(path))
        answer = "c"
        while answer not in ["y", "Y", "n", "N", ""]:
            answer = input("Shall these directories be removed (y/N)? ")
            # secok
        if answer in ["y", "Y"]:
            for path in pathsToRemove:
                shutil.rmtree(path)


def removeDataDirectory():
    """
    Remove the eric data directory.
    """
    cfg = getConfigDir()
    if os.path.exists(cfg):
        print("Found the eric data directory")
        print("  - {0}".format(cfg))
        answer = "c"
        while answer not in ["y", "Y", "n", "N", ""]:
            answer = input("Shall this directory be removed (y/N)? ")
            # secok
        if answer in ["y", "Y"]:
            shutil.rmtree(cfg)


def removeConfigurationData():
    """
    Remove the eric configuration directory.
    """
    try:
        from PyQt6.QtCore import QSettings  # __IGNORE_WARNING_I10__
    except ImportError:
        print("No PyQt variant installed. The configuration directory")
        print("cannot be determined. You have to remove it manually.\n")
        return

    settings = QSettings(
        QSettings.Format.IniFormat,
        QSettings.Scope.UserScope,
        settingsNameOrganization,
        settingsNameGlobal,
    )
    settingsDir = os.path.dirname(settings.fileName())
    if os.path.exists(settingsDir):
        print("Found the eric configuration directory")
        print("  - {0}".format(settingsDir))
        answer = "c"
        while answer not in ["y", "Y", "n", "N", ""]:
            answer = input("Shall this directory be removed (y/N)? ")
            # secok
        if answer in ["y", "Y"]:
            shutil.rmtree(settingsDir)


def getConfigDir():
    """
    Module function to get the name of the directory storing the config data.

    @return directory name of the config dir
    @rtype str
    """
    cdn = ".eric7"
    return os.path.join(os.path.expanduser("~"), cdn)


def getWinregEntry(name, path):
    """
    Function to get an entry from the Windows Registry.

    @param name variable name
    @type str
    @param path registry path of the variable
    @type str
    @return value of requested registry variable
    @rtype Any
    """
    # From http://stackoverflow.com/a/35286642
    import winreg  # __IGNORE_WARNING_I103__

    try:
        registryKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, winreg.KEY_READ)
        value, _ = winreg.QueryValueEx(registryKey, name)
        winreg.CloseKey(registryKey)
        return value
    except WindowsError:
        return None


def windowsDesktopNames():
    """
    Function to generate the link names for the Windows Desktop.

    @return list of desktop link names
    @rtype list of str
    """
    majorVersion, minorVersion = sys.version_info[:2]
    linkTemplates = [
        "eric7 IDE (Python {0}.{1}).lnk",
        "eric7 Browser (Python {0}.{1}).lnk",
        "eric7 MicroPython (Python {0}.{1}).lnk",
        # obsolete entries below
        "eric7 (Python {0}.{1}).lnk",
    ]

    return [ll.format(majorVersion, minorVersion) for ll in linkTemplates]


def windowsProgramsEntry():
    """
    Function to generate the name of the Start Menu top entry.

    @return name of the Start Menu top entry
    @rtype str
    """
    majorVersion, minorVersion = sys.version_info[:2]
    return "eric7 (Python {0}.{1})".format(majorVersion, minorVersion)


def createArgumentParser():
    """
    Function to create an argument parser.

    @return created argument parser object
    @rtype argparse.ArgumentParser
    """
    parser = argparse.ArgumentParser(description="Uninstall eric7.")
    return parser


def main():
    """
    The main function of the script.
    """
    initGlobals()

    parser = createArgumentParser()
    parser.parse_args()

    print("\nUninstalling eric ...")
    uninstallEric()
    print("\nUninstallation complete.")
    print()

    exit(0)


if __name__ == "__main__":
    try:
        main()
    except SystemExit:
        raise
    except Exception:
        print(
            """An internal error occured.  Please report all the output of"""
            """ the program,\n"""
            """including the following traceback, to"""
            """ eric-bugs@eric-ide.python-projects.org.\n"""
        )
        raise

#
# eflag: noqa = M801

eric ide

mercurial