]> git.saurik.com Git - wxWidgets.git/blame - wxPython/distrib/mac/uninstall_wxPython.py
Don't uninstall common files if there is more than one wxPython installed
[wxWidgets.git] / wxPython / distrib / mac / uninstall_wxPython.py
CommitLineData
85245f48
RD
1#!/usr/bin/env python
2"""
3This script will search for installed versions of wxPython on OSX and
4allow the user to choose one to uninstall. It then will use the
5metadata stored about the installed package to remove all the files
6associated with that install.
7
8Only the files installed by the main Installer Package will be
9removed. This includes the Python modules and the wxWidgets shared
10libraries. If you also installed the demo or docs by dragging them out
11of the disk image, then you will need to drag them to the Trash
12yourself.
13"""
14
15import sys, os, glob
45b32352 16from fnmatch import fnmatchcase
85245f48
RD
17import cPickle, urllib
18
19RCPTDIR = "/Library/Receipts"
20RSRCDIR = "Contents/Resources"
21
45b32352
RD
22# Only completly clean out dirs that have one of these as a prefix.
23# We do this because the file list returned from lsbom will include /,
24# /usr, /usr/local, etc.
85245f48
RD
25PREFIXES = [ '/Library/Python/2.3/',
26 '/Library/Python/2.4/',
45b32352
RD
27 '/Library/Frameworks/Python.framework/Versions/2.3/lib/python2.3/site-pacakges/',
28 '/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-pacakges/',
85245f48
RD
29 '/usr/local/lib/',
30 ]
31
45b32352
RD
32# The files that match one of the items in this list will only be
33# removed if the last installation of wxPython on the system is being
34# uninstalled.
35COMMON_FILES = [ '/usr/local/bin/*',
36 'wx.pth',
37 'wxversion.py',
38 ]
39
40
85245f48
RD
41
42class AccessError(Exception):
43 pass
44
45
46class InstalledReceipt(object):
47 def __init__(self, rcptPath):
48 self.rcptPath = rcptPath
49 self.rsrcPath = os.path.join(rcptPath, RSRCDIR)
50 self.bomFile = glob.glob(os.path.join(self.rsrcPath, "*.bom"))[0]
51 self.findMetaData()
52
53
54 def findMetaData(self):
55 # TODO: Make this be able to also look at Info.plist files
56 infoFile = glob.glob(os.path.join(self.rsrcPath, "*.info"))[0]
57 self.mdata = {}
58 for line in open(infoFile, "r").readlines():
59 line = line.strip()
60 if line and line[0] != '#':
61 ls = line.split()
62 self.mdata[ls[0]] = line[len(ls[0])+1:]
63
64
65 def getFileList(self):
66 p = os.popen("lsbom -s %s" % self.bomFile, "r")
67 data = p.read()
68 data.strip()
69 data = filter(lambda s: s!='' and s!='.', data.split('\n'))
70 loc = self.mdata['DefaultLocation']
71 return [loc+item for item in data]
72
73
74 def walkFiles(self, handleFile, handleDir):
75 dirs = []
76 names = self.getFileList()
77
78 # the plain files
79 for name in names:
80 name = os.path.abspath(name)
81 if os.path.isdir(name):
82 dirs.append(name)
83 else:
84 handleFile(name)
85
86 # the directories
87 dirs.reverse()
88 for dir in dirs:
89 for prefix in PREFIXES:
90 if dir.startswith(prefix):
91 handleDir(dir)
92 break
93
94 # Finally, remove the Receipts package, bottom-up
95 for dirpath, dirname, filenames in os.walk(self.rcptPath, False):
96 for name in filenames:
97 name = os.path.join(dirpath, name)
98 handleFile(name)
99 handleDir(dirpath)
100
101
45b32352
RD
102 def testCommon(self, name):
103 for cmn in COMMON_FILES:
104 if fnmatchcase(name, cmn) or fnmatchcase(os.path.basename(name), cmn):
105 return True
106 return False
107
85245f48
RD
108
109 def showFiles(self):
110 def show(name):
111 if os.path.exists(name):
45b32352
RD
112 if not self.lastInstall and self.testCommon(name):
113 return
85245f48
RD
114 print "Will remove:", name
115 self.walkFiles(show, show)
116
117
118 def testUninstallAccess(self):
119 def testFile(name):
120 if os.path.exists(name):
45b32352
RD
121 if not self.lastInstall and self.testCommon(name):
122 return
85245f48
RD
123 if not os.access(name, os.W_OK):
124 raise AccessError(name)
125 self.walkFiles(testFile, testFile)
126
127
128 def doUninstall(self):
129 def removeFile(name):
130 if os.path.exists(name):
45b32352
RD
131 if not self.lastInstall and self.testCommon(name):
132 return
85245f48
RD
133 print "Removing:", name
134 os.unlink(name)
135 def removeDir(name):
136 print "Removing:", name
137 if os.path.exists(name):
138 hasFiles = os.listdir(name)
45b32352 139 if hasFiles: # perhaps some stale symlinks, or .pyc files
85245f48
RD
140 for file in hasFiles:
141 os.unlink(os.path.join(name, file))
142 os.rmdir(name)
143
144 try:
145 self.testUninstallAccess()
146 except AccessError, e:
147 print "UNABLE TO UNINSTALL!\nNo permission to remove: ", e.args[0]
148 sys.exit()
149
150 self.walkFiles(removeFile, removeDir)
151
152
153
154
155def findInstalled():
156 installed = []
157 for name in glob.glob(os.path.join(RCPTDIR, "wxPython*")):
158 ir = InstalledReceipt(name)
159 installed.append(ir)
160
161 return installed
162
163
164# Just in case a Python < 2.3 is used to run this
165try:
166 enumerate
167except NameError:
168 def enumerate(sequence):
169 return zip(range(len(sequence)), sequence)
170
171
172def main():
173 if len(sys.argv) > 1 and sys.argv[1] == "-doit":
174 inst = cPickle.loads(urllib.unquote(sys.argv[2]))
175 inst.doUninstall()
176 sys.exit()
177
178 print __doc__
179 installed = findInstalled()
180
181 if not installed:
182 print "*** No wxPython installations found! ***"
183 raw_input("Press RETURN...")
184 sys.exit()
185
186 for i, inst in enumerate(installed):
187 print " %d. %s \t%s" % (i+1, inst.mdata["Title"], inst.mdata["Version"])
188
189 print
190 ans = raw_input("Enter the number of the install to examine or 'Q' to quit: ")
191 if ans in ['Q', 'q']:
192 sys.exit()
193 inst = installed[int(ans) - 1]
45b32352 194 inst.lastInstall = len(installed) == 1
85245f48
RD
195
196 while True:
197 print
198 print """
199 Title: %(Title)s
200 Version: %(Version)s
201 Description: %(Description)s
202 """ % inst.mdata
203
204 ans = raw_input("(U)ninstall, (S)how what will be removed, or (Q)uit? [u,s,q] ")
205 if ans in ['Q', 'q']:
206 sys.exit()
207
208 elif ans in ['S', 's']:
209 inst.showFiles()
210
211 elif ans in ['U', 'u']:
212 print
213 print "Launching uninstaller with sudo, please enter your password if prompted:"
214 os.system("sudo %s -doit %s" %
215 (sys.argv[0],
216 urllib.quote(cPickle.dumps(inst))))
217 sys.exit()
218
219
220if __name__ == '__main__':
221 main()