uninstall_cleanup.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. #!/usr/bin/env python
  2. # This code runs compatibly under Python 2 and 3.x for x >= 2.
  3. # Preserve this property!
  4. from __future__ import absolute_import, print_function, division
  5. import glob
  6. import os
  7. import subprocess
  8. import sys
  9. GPS_LIB_NAME = 'gps'
  10. BINARY_ENCODING = 'latin-1'
  11. if bytes is str:
  12. polystr = str
  13. else: # Otherwise we do something real
  14. def polystr(o):
  15. "Convert bytes or str to str with proper encoding."
  16. if isinstance(o, str):
  17. return o
  18. if isinstance(o, bytes):
  19. return str(o, encoding=BINARY_ENCODING)
  20. raise ValueError
  21. def DoCommand(cmd_list):
  22. "Perform external command, returning exit code and stdout."
  23. pipe = subprocess.PIPE
  24. proc = subprocess.Popen(cmd_list, stdin=pipe, stdout=pipe)
  25. result, _ = proc.communicate()
  26. return proc.returncode, polystr(result)
  27. class PythonCommand(object):
  28. "Object for one system Python command."
  29. PYTHON_GLOB = 'python*'
  30. TEXT_PREFIX = b'#!'
  31. PATH_ENV = 'PATH'
  32. PATH_ENV_SEP = ':'
  33. PYTHON_EXE_COMMANDS = [
  34. 'import sys',
  35. 'print(sys.executable)',
  36. ]
  37. instances = []
  38. def __init__(self, command):
  39. "Set up PythonCommand."
  40. self.command = command
  41. @classmethod
  42. def FindPythons(cls, dir):
  43. "Create PythonCommand objects by scanning directory."
  44. pattern = dir + os.path.sep + cls.PYTHON_GLOB
  45. pythons = glob.glob(pattern)
  46. for python in pythons:
  47. with open(python, 'rb') as f:
  48. if f.read(2) == cls.TEXT_PREFIX:
  49. continue
  50. cls.instances.append(cls(python))
  51. return cls.instances
  52. @classmethod
  53. def FindAllPythons(cls):
  54. "Create PythonCommand objects by scanning command PATH."
  55. paths = os.getenv(cls.PATH_ENV)
  56. for dir in paths.split(cls.PATH_ENV_SEP):
  57. cls.FindPythons(dir)
  58. return cls.instances
  59. def GetExecutable(self):
  60. "Obtain executable path from this Python."
  61. command = [self.command, '-c', ';'.join(self.PYTHON_EXE_COMMANDS)]
  62. status, result = DoCommand(command)
  63. if status:
  64. return None
  65. return result.strip()
  66. class PythonExecutable(object):
  67. "Object for one Python executable, deduped."
  68. PYTHON_LIBDIR_COMMANDS = [
  69. 'from distutils import sysconfig',
  70. 'print(sysconfig.get_python_lib())',
  71. ]
  72. _by_path = {}
  73. def __new__(cls, command):
  74. "Create or update one PythonExecutable from PythonCommand."
  75. path = command.GetExecutable()
  76. existing = cls._by_path.get(path)
  77. if existing:
  78. existing.commands.append(command)
  79. return existing
  80. self = super(PythonExecutable, cls).__new__(cls)
  81. self.commands = [command]
  82. self.path = path
  83. self.libdir = None
  84. cls._by_path[path] = self
  85. return self
  86. def __lt__(self, other):
  87. "Allow sorting."
  88. return self.path < other.path
  89. @classmethod
  90. def GetAllExecutables(cls, command_list):
  91. "Build list of executables from list of commands."
  92. for command in command_list:
  93. cls(command)
  94. return sorted(cls._by_path.values())
  95. def GetLibdir(self):
  96. "Obtain libdir path from this Python."
  97. if self.libdir:
  98. return self.libdir
  99. command = [self.path, '-c', ';'.join(self.PYTHON_LIBDIR_COMMANDS)]
  100. status, result = DoCommand(command)
  101. if status:
  102. return None
  103. return result.strip()
  104. def CleanLib(self, name):
  105. "Clean up given package from this Python."
  106. dir = os.path.join(self.GetLibdir(), name)
  107. if not name or not os.path.exists(dir):
  108. return
  109. try:
  110. os.rmdir(dir)
  111. except OSError:
  112. print('Unable to remove %s' % dir)
  113. else:
  114. print('Removed empty %s' % dir)
  115. @classmethod
  116. def CleanAllLibs(cls, name):
  117. "Clean up given package from all executables."
  118. for exe in cls._by_path.values():
  119. exe.CleanLib(name)
  120. def main(argv):
  121. "Main function."
  122. commands = PythonCommand.FindAllPythons()
  123. PythonExecutable.GetAllExecutables(commands)
  124. PythonExecutable.CleanAllLibs(GPS_LIB_NAME)
  125. return 0
  126. if __name__ == '__main__':
  127. sys.exit(main(sys.argv))