uninstall_cleanup.py 4.5 KB

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