diff options
Diffstat (limited to 'devtools/uninstall_cleanup.py')
-rwxr-xr-x | devtools/uninstall_cleanup.py | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/devtools/uninstall_cleanup.py b/devtools/uninstall_cleanup.py new file mode 100755 index 00000000..41be7a8b --- /dev/null +++ b/devtools/uninstall_cleanup.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python + +# This code runs compatibly under Python 2 and 3.x for x >= 2. +# Preserve this property! +from __future__ import absolute_import, print_function, division + +import glob, os, subprocess, sys + +GPS_LIB_NAME = 'gps' + +BINARY_ENCODING = 'latin-1' + +if bytes is str: + + polystr = str + +else: # Otherwise we do something real + + def polystr(o): + "Convert bytes or str to str with proper encoding." + if isinstance(o, str): + return o + if isinstance(o, bytes): + return str(o, encoding=BINARY_ENCODING) + raise ValueError + + +def DoCommand(cmd_list): + "Perform external command, returning exit code and stdout." + pipe = subprocess.PIPE + proc = subprocess.Popen(cmd_list, stdin=pipe, stdout=pipe) + result, _ = proc.communicate() + return proc.returncode, polystr(result) + + +class PythonCommand(object): + "Object for one system Python command." + PYTHON_GLOB = 'python*' + TEXT_PREFIX = b'#!' + PATH_ENV = 'PATH' + PATH_ENV_SEP = ':' + PYTHON_EXE_COMMANDS = [ + 'import sys', + 'print(sys.executable)', + ] + + instances = [] + + def __init__(self, command): + "Set up PythonCommand." + self.command = command + + @classmethod + def FindPythons(cls, dir): + "Create PythonCommand objects by scanning directory." + pattern = dir + os.path.sep + cls.PYTHON_GLOB + pythons = glob.glob(pattern) + for python in pythons: + with open(python, 'rb') as f: + if f.read(2) == cls.TEXT_PREFIX: + continue + cls.instances.append(cls(python)) + return cls.instances + + @classmethod + def FindAllPythons(cls): + "Create PythonCommand objects by scanning command PATH." + paths = os.getenv(cls.PATH_ENV) + for dir in paths.split(cls.PATH_ENV_SEP): + cls.FindPythons(dir) + return cls.instances + + def GetExecutable(self): + "Obtain executable path from this Python." + command = [self.command, '-c', ';'.join(self.PYTHON_EXE_COMMANDS)] + status, result = DoCommand(command) + if status: + return None + return result.strip() + + +class PythonExecutable(object): + "Object for one Python executable, deduped." + PYTHON_LIBDIR_COMMANDS = [ + 'from distutils import sysconfig', + 'print(sysconfig.get_python_lib())', + ] + + _by_path = {} + + def __new__(cls, command): + "Create or update one PythonExecutable from PythonCommand." + path = command.GetExecutable() + existing = cls._by_path.get(path) + if existing: + existing.commands.append(command) + return existing + self = super(PythonExecutable, cls).__new__(cls) + self.commands = [command] + self.path = path + self.libdir = None + cls._by_path[path] = self + return self + + def __lt__(self, other): + "Allow sorting." + return self.path < other.path + + @classmethod + def GetAllExecutables(cls, command_list): + "Build list of executables from list of commands." + for command in command_list: + cls(command) + return sorted(cls._by_path.values()) + + def GetLibdir(self): + "Obtain libdir path from this Python." + if self.libdir: + return self.libdir + command = [self.path, '-c', ';'.join(self.PYTHON_LIBDIR_COMMANDS)] + status, result = DoCommand(command) + if status: + return None + return result.strip() + + def CleanLib(self, name): + "Clean up given package from this Python." + dir = os.path.join(self.GetLibdir(), name) + if not name or not os.path.exists(dir): + return + try: + os.rmdir(dir) + except OSError: + print('Unable to remove %s' % dir) + else: + print('Removed empty %s' % dir) + + @classmethod + def CleanAllLibs(cls, name): + "Clean up given package from all executables." + for exe in cls._by_path.values(): + exe.CleanLib(name) + + +def main(argv): + "Main function." + commands = PythonCommand.FindAllPythons() + PythonExecutable.GetAllExecutables(commands) + PythonExecutable.CleanAllLibs(GPS_LIB_NAME) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) |