gpsd-dev
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gpsd-dev] [PATCH 2/4] Adds a tool for cleaning up after Python uninstal


From: Fred Wright
Subject: [gpsd-dev] [PATCH 2/4] Adds a tool for cleaning up after Python uninstall bug.
Date: Sat, 16 Apr 2016 00:01:11 -0700

Since the just-fixed SConstruct bug could have left empty Python
package directories lying around in multiple Python installs,
this provides a tool for cleaning them all up.  It applies to
every non-text python* file in the command PATH, and removes
any empty 'gps' package directories.  Nonempty directories are not
removed.

TESTED:
Verified that directories left over by the uncorrected uninstall were
removed, and that nonempty directories weren't.  Tested with both
Python 2 and Python 3.
---
 devtools/uninstall_cleanup.py | 154 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 154 insertions(+)
 create mode 100755 devtools/uninstall_cleanup.py

diff --git a/devtools/uninstall_cleanup.py b/devtools/uninstall_cleanup.py
new file mode 100755
index 0000000..41be7a8
--- /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))
-- 
2.8.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]