blob: 9a3e82e26919cecfd87f851b240c5cef77b05204 [file] [log] [blame]
Jack Jansen6a600ab2003-02-10 15:55:51 +00001"""Package Install Manager for Python.
2
Tim Peters182b5ac2004-07-18 06:16:08 +00003This is currently a MacOSX-only strawman implementation.
Jack Jansenc5991b02003-06-29 00:09:18 +00004Despite other rumours the name stands for "Packman IMPlementation".
Jack Jansen6a600ab2003-02-10 15:55:51 +00005
6Tools to allow easy installation of packages. The idea is that there is
7an online XML database per (platform, python-version) containing packages
8known to work with that combination. This module contains tools for getting
9and parsing the database, testing whether packages are installed, computing
10dependencies and installing packages.
11
12There is a minimal main program that works as a command line tool, but the
13intention is that the end user will use this through a GUI.
14"""
Benjamin Peterson23681932008-05-12 21:42:13 +000015
16from warnings import warnpy3k
Benjamin Petersona6864e02008-07-14 17:42:17 +000017warnpy3k("In 3.x, the pimp module is removed.", stacklevel=2)
Benjamin Peterson23681932008-05-12 21:42:13 +000018
Jack Jansen95839b82003-02-09 23:10:20 +000019import sys
20import os
Neal Norwitz42dd86b2007-05-11 06:57:33 +000021import subprocess
Jack Jansen95839b82003-02-09 23:10:20 +000022import urllib
Jack Jansen47e59872003-03-11 14:37:19 +000023import urllib2
Jack Jansen95839b82003-02-09 23:10:20 +000024import urlparse
25import plistlib
26import distutils.util
Jack Jansene71b9f82003-02-12 16:37:00 +000027import distutils.sysconfig
Georg Brandlbffb0bc2006-04-30 08:57:35 +000028import hashlib
Jack Jansen6fde1ce2003-04-15 14:43:05 +000029import tarfile
30import tempfile
31import shutil
Jack Jansen989ddc02004-03-11 23:03:59 +000032import time
Jack Jansen95839b82003-02-09 23:10:20 +000033
Tim Peters182b5ac2004-07-18 06:16:08 +000034__all__ = ["PimpPreferences", "PimpDatabase", "PimpPackage", "main",
Jack Jansen702af672004-12-28 21:33:27 +000035 "getDefaultDatabase", "PIMP_VERSION", "main"]
Jack Jansen6a600ab2003-02-10 15:55:51 +000036
Jack Jansen95839b82003-02-09 23:10:20 +000037_scriptExc_NotInstalled = "pimp._scriptExc_NotInstalled"
38_scriptExc_OldInstalled = "pimp._scriptExc_OldInstalled"
39_scriptExc_BadInstalled = "pimp._scriptExc_BadInstalled"
40
41NO_EXECUTE=0
42
Jack Jansenaf304a62005-01-03 15:44:18 +000043PIMP_VERSION="0.5"
Jack Jansene7b33db2003-02-11 22:40:59 +000044
Jack Jansen0dacac42003-02-14 14:11:59 +000045# Flavors:
46# source: setup-based package
47# binary: tar (or other) archive created with setup.py bdist.
Jack Jansenaf304a62005-01-03 15:44:18 +000048# installer: something that can be opened
49DEFAULT_FLAVORORDER=['source', 'binary', 'installer']
Jack Jansen95839b82003-02-09 23:10:20 +000050DEFAULT_DOWNLOADDIR='/tmp'
51DEFAULT_BUILDDIR='/tmp'
Jack Jansene71b9f82003-02-12 16:37:00 +000052DEFAULT_INSTALLDIR=distutils.sysconfig.get_python_lib()
Jack Jansenafd63b92004-02-28 22:34:02 +000053DEFAULT_PIMPDATABASE_FMT="http://www.python.org/packman/version-%s/%s-%s-%s-%s-%s.plist"
Jack Jansen95839b82003-02-09 23:10:20 +000054
Jack Jansen192bd962004-02-28 23:18:43 +000055def getDefaultDatabase(experimental=False):
Jack Jansen989ddc02004-03-11 23:03:59 +000056 if experimental:
57 status = "exp"
58 else:
59 status = "prod"
Tim Peters182b5ac2004-07-18 06:16:08 +000060
Jack Jansen989ddc02004-03-11 23:03:59 +000061 major, minor, micro, state, extra = sys.version_info
62 pyvers = '%d.%d' % (major, minor)
Jack Jansen702af672004-12-28 21:33:27 +000063 if micro == 0 and state != 'final':
Jack Jansen989ddc02004-03-11 23:03:59 +000064 pyvers = pyvers + '%s%d' % (state, extra)
Tim Peters182b5ac2004-07-18 06:16:08 +000065
Jack Jansen989ddc02004-03-11 23:03:59 +000066 longplatform = distutils.util.get_platform()
67 osname, release, machine = longplatform.split('-')
68 # For some platforms we may want to differentiate between
69 # installation types
70 if osname == 'darwin':
71 if sys.prefix.startswith('/System/Library/Frameworks/Python.framework'):
72 osname = 'darwin_apple'
73 elif sys.prefix.startswith('/Library/Frameworks/Python.framework'):
74 osname = 'darwin_macpython'
75 # Otherwise we don't know...
76 # Now we try various URLs by playing with the release string.
77 # We remove numbers off the end until we find a match.
78 rel = release
79 while True:
80 url = DEFAULT_PIMPDATABASE_FMT % (PIMP_VERSION, status, pyvers, osname, rel, machine)
81 try:
82 urllib2.urlopen(url)
83 except urllib2.HTTPError, arg:
84 pass
85 else:
86 break
87 if not rel:
88 # We're out of version numbers to try. Use the
89 # full release number, this will give a reasonable
90 # error message later
91 url = DEFAULT_PIMPDATABASE_FMT % (PIMP_VERSION, status, pyvers, osname, release, machine)
92 break
93 idx = rel.rfind('.')
94 if idx < 0:
95 rel = ''
96 else:
97 rel = rel[:idx]
98 return url
Jack Jansen192bd962004-02-28 23:18:43 +000099
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000100def _cmd(output, dir, *cmditems):
101 """Internal routine to run a shell command in a given directory."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000102
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000103 cmd = ("cd \"%s\"; " % dir) + " ".join(cmditems)
104 if output:
105 output.write("+ %s\n" % cmd)
106 if NO_EXECUTE:
107 return 0
Neal Norwitz42dd86b2007-05-11 06:57:33 +0000108 child = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
109 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
110 child.stdin.close()
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000111 while 1:
Neal Norwitz42dd86b2007-05-11 06:57:33 +0000112 line = child.stdout.readline()
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000113 if not line:
114 break
115 if output:
116 output.write(line)
117 return child.wait()
118
Jack Jansen989ddc02004-03-11 23:03:59 +0000119class PimpDownloader:
120 """Abstract base class - Downloader for archives"""
Tim Peters182b5ac2004-07-18 06:16:08 +0000121
Jack Jansen989ddc02004-03-11 23:03:59 +0000122 def __init__(self, argument,
123 dir="",
124 watcher=None):
125 self.argument = argument
126 self._dir = dir
127 self._watcher = watcher
Tim Peters182b5ac2004-07-18 06:16:08 +0000128
Jack Jansen989ddc02004-03-11 23:03:59 +0000129 def download(self, url, filename, output=None):
130 return None
Tim Peters182b5ac2004-07-18 06:16:08 +0000131
Jack Jansen989ddc02004-03-11 23:03:59 +0000132 def update(self, str):
133 if self._watcher:
134 return self._watcher.update(str)
135 return True
Tim Peters182b5ac2004-07-18 06:16:08 +0000136
Jack Jansen989ddc02004-03-11 23:03:59 +0000137class PimpCurlDownloader(PimpDownloader):
138
139 def download(self, url, filename, output=None):
140 self.update("Downloading %s..." % url)
141 exitstatus = _cmd(output, self._dir,
142 "curl",
143 "--output", filename,
144 url)
145 self.update("Downloading %s: finished" % url)
146 return (not exitstatus)
Tim Peters182b5ac2004-07-18 06:16:08 +0000147
Jack Jansen989ddc02004-03-11 23:03:59 +0000148class PimpUrllibDownloader(PimpDownloader):
149
150 def download(self, url, filename, output=None):
151 output = open(filename, 'wb')
152 self.update("Downloading %s: opening connection" % url)
153 keepgoing = True
154 download = urllib2.urlopen(url)
Mark Dickinsona3a50502010-04-03 18:17:54 +0000155 if 'content-length' in download.headers:
Jack Jansen989ddc02004-03-11 23:03:59 +0000156 length = long(download.headers['content-length'])
157 else:
158 length = -1
Tim Peters182b5ac2004-07-18 06:16:08 +0000159
Jack Jansen989ddc02004-03-11 23:03:59 +0000160 data = download.read(4096) #read 4K at a time
161 dlsize = 0
162 lasttime = 0
163 while keepgoing:
164 dlsize = dlsize + len(data)
Tim Peters182b5ac2004-07-18 06:16:08 +0000165 if len(data) == 0:
Jack Jansen989ddc02004-03-11 23:03:59 +0000166 #this is our exit condition
167 break
168 output.write(data)
169 if int(time.time()) != lasttime:
170 # Update at most once per second
171 lasttime = int(time.time())
172 if length == -1:
173 keepgoing = self.update("Downloading %s: %d bytes..." % (url, dlsize))
174 else:
175 keepgoing = self.update("Downloading %s: %d%% (%d bytes)..." % (url, int(100.0*dlsize/length), dlsize))
176 data = download.read(4096)
177 if keepgoing:
178 self.update("Downloading %s: finished" % url)
179 return keepgoing
Tim Peters182b5ac2004-07-18 06:16:08 +0000180
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000181class PimpUnpacker:
182 """Abstract base class - Unpacker for archives"""
Tim Peters182b5ac2004-07-18 06:16:08 +0000183
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000184 _can_rename = False
Tim Peters182b5ac2004-07-18 06:16:08 +0000185
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000186 def __init__(self, argument,
187 dir="",
Jack Jansen989ddc02004-03-11 23:03:59 +0000188 renames=[],
189 watcher=None):
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000190 self.argument = argument
191 if renames and not self._can_rename:
192 raise RuntimeError, "This unpacker cannot rename files"
193 self._dir = dir
194 self._renames = renames
Jack Jansen989ddc02004-03-11 23:03:59 +0000195 self._watcher = watcher
Tim Peters182b5ac2004-07-18 06:16:08 +0000196
Jack Jansen5da131b2003-06-01 20:57:12 +0000197 def unpack(self, archive, output=None, package=None):
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000198 return None
Tim Peters182b5ac2004-07-18 06:16:08 +0000199
Jack Jansen989ddc02004-03-11 23:03:59 +0000200 def update(self, str):
201 if self._watcher:
202 return self._watcher.update(str)
203 return True
Tim Peters182b5ac2004-07-18 06:16:08 +0000204
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000205class PimpCommandUnpacker(PimpUnpacker):
206 """Unpack archives by calling a Unix utility"""
Tim Peters182b5ac2004-07-18 06:16:08 +0000207
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000208 _can_rename = False
Tim Peters182b5ac2004-07-18 06:16:08 +0000209
Jack Jansen5da131b2003-06-01 20:57:12 +0000210 def unpack(self, archive, output=None, package=None):
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000211 cmd = self.argument % archive
212 if _cmd(output, self._dir, cmd):
213 return "unpack command failed"
Tim Peters182b5ac2004-07-18 06:16:08 +0000214
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000215class PimpTarUnpacker(PimpUnpacker):
216 """Unpack tarfiles using the builtin tarfile module"""
Tim Peters182b5ac2004-07-18 06:16:08 +0000217
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000218 _can_rename = True
Tim Peters182b5ac2004-07-18 06:16:08 +0000219
Jack Jansen5da131b2003-06-01 20:57:12 +0000220 def unpack(self, archive, output=None, package=None):
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000221 tf = tarfile.open(archive, "r")
222 members = tf.getmembers()
223 skip = []
224 if self._renames:
225 for member in members:
226 for oldprefix, newprefix in self._renames:
227 if oldprefix[:len(self._dir)] == self._dir:
228 oldprefix2 = oldprefix[len(self._dir):]
229 else:
230 oldprefix2 = None
231 if member.name[:len(oldprefix)] == oldprefix:
232 if newprefix is None:
233 skip.append(member)
234 #print 'SKIP', member.name
235 else:
236 member.name = newprefix + member.name[len(oldprefix):]
237 print ' ', member.name
238 break
239 elif oldprefix2 and member.name[:len(oldprefix2)] == oldprefix2:
240 if newprefix is None:
241 skip.append(member)
242 #print 'SKIP', member.name
243 else:
244 member.name = newprefix + member.name[len(oldprefix2):]
245 #print ' ', member.name
246 break
247 else:
248 skip.append(member)
249 #print '????', member.name
250 for member in members:
251 if member in skip:
Jack Jansen989ddc02004-03-11 23:03:59 +0000252 self.update("Skipping %s" % member.name)
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000253 continue
Jack Jansen989ddc02004-03-11 23:03:59 +0000254 self.update("Extracting %s" % member.name)
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000255 tf.extract(member, self._dir)
256 if skip:
257 names = [member.name for member in skip if member.name[-1] != '/']
Jack Jansen5da131b2003-06-01 20:57:12 +0000258 if package:
259 names = package.filterExpectedSkips(names)
Jack Jansen6432f782003-04-22 13:56:19 +0000260 if names:
Jack Jansen705553a2003-05-06 12:44:00 +0000261 return "Not all files were unpacked: %s" % " ".join(names)
Tim Peters182b5ac2004-07-18 06:16:08 +0000262
Jack Jansen95839b82003-02-09 23:10:20 +0000263ARCHIVE_FORMATS = [
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000264 (".tar.Z", PimpTarUnpacker, None),
265 (".taz", PimpTarUnpacker, None),
266 (".tar.gz", PimpTarUnpacker, None),
267 (".tgz", PimpTarUnpacker, None),
268 (".tar.bz", PimpTarUnpacker, None),
269 (".zip", PimpCommandUnpacker, "unzip \"%s\""),
Jack Jansen95839b82003-02-09 23:10:20 +0000270]
271
272class PimpPreferences:
Jack Jansen0ae32202003-04-09 13:25:43 +0000273 """Container for per-user preferences, such as the database to use
274 and where to install packages."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000275
276 def __init__(self,
Jack Jansen0ae32202003-04-09 13:25:43 +0000277 flavorOrder=None,
278 downloadDir=None,
279 buildDir=None,
280 installDir=None,
281 pimpDatabase=None):
282 if not flavorOrder:
283 flavorOrder = DEFAULT_FLAVORORDER
284 if not downloadDir:
285 downloadDir = DEFAULT_DOWNLOADDIR
286 if not buildDir:
287 buildDir = DEFAULT_BUILDDIR
Jack Jansen0ae32202003-04-09 13:25:43 +0000288 if not pimpDatabase:
Jack Jansen192bd962004-02-28 23:18:43 +0000289 pimpDatabase = getDefaultDatabase()
Jack Jansen20fa6752003-04-16 12:15:34 +0000290 self.setInstallDir(installDir)
291 self.flavorOrder = flavorOrder
292 self.downloadDir = downloadDir
293 self.buildDir = buildDir
294 self.pimpDatabase = pimpDatabase
Jack Jansen989ddc02004-03-11 23:03:59 +0000295 self.watcher = None
Tim Peters182b5ac2004-07-18 06:16:08 +0000296
Jack Jansen989ddc02004-03-11 23:03:59 +0000297 def setWatcher(self, watcher):
298 self.watcher = watcher
Tim Peters182b5ac2004-07-18 06:16:08 +0000299
Jack Jansen20fa6752003-04-16 12:15:34 +0000300 def setInstallDir(self, installDir=None):
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000301 if installDir:
302 # Installing to non-standard location.
303 self.installLocations = [
304 ('--install-lib', installDir),
305 ('--install-headers', None),
306 ('--install-scripts', None),
307 ('--install-data', None)]
308 else:
309 installDir = DEFAULT_INSTALLDIR
310 self.installLocations = []
Jack Jansen0ae32202003-04-09 13:25:43 +0000311 self.installDir = installDir
Tim Peters182b5ac2004-07-18 06:16:08 +0000312
Jack Jansen5da131b2003-06-01 20:57:12 +0000313 def isUserInstall(self):
314 return self.installDir != DEFAULT_INSTALLDIR
Jack Jansen20fa6752003-04-16 12:15:34 +0000315
Jack Jansen0ae32202003-04-09 13:25:43 +0000316 def check(self):
317 """Check that the preferences make sense: directories exist and are
318 writable, the install directory is on sys.path, etc."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000319
Jack Jansen0ae32202003-04-09 13:25:43 +0000320 rv = ""
321 RWX_OK = os.R_OK|os.W_OK|os.X_OK
322 if not os.path.exists(self.downloadDir):
323 rv += "Warning: Download directory \"%s\" does not exist\n" % self.downloadDir
324 elif not os.access(self.downloadDir, RWX_OK):
325 rv += "Warning: Download directory \"%s\" is not writable or not readable\n" % self.downloadDir
326 if not os.path.exists(self.buildDir):
327 rv += "Warning: Build directory \"%s\" does not exist\n" % self.buildDir
328 elif not os.access(self.buildDir, RWX_OK):
329 rv += "Warning: Build directory \"%s\" is not writable or not readable\n" % self.buildDir
330 if not os.path.exists(self.installDir):
331 rv += "Warning: Install directory \"%s\" does not exist\n" % self.installDir
332 elif not os.access(self.installDir, RWX_OK):
333 rv += "Warning: Install directory \"%s\" is not writable or not readable\n" % self.installDir
334 else:
335 installDir = os.path.realpath(self.installDir)
336 for p in sys.path:
337 try:
338 realpath = os.path.realpath(p)
339 except:
340 pass
341 if installDir == realpath:
342 break
343 else:
344 rv += "Warning: Install directory \"%s\" is not on sys.path\n" % self.installDir
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000345 return rv
Tim Peters182b5ac2004-07-18 06:16:08 +0000346
Jack Jansen0ae32202003-04-09 13:25:43 +0000347 def compareFlavors(self, left, right):
348 """Compare two flavor strings. This is part of your preferences
349 because whether the user prefers installing from source or binary is."""
350 if left in self.flavorOrder:
351 if right in self.flavorOrder:
352 return cmp(self.flavorOrder.index(left), self.flavorOrder.index(right))
353 return -1
354 if right in self.flavorOrder:
355 return 1
356 return cmp(left, right)
Tim Peters182b5ac2004-07-18 06:16:08 +0000357
Jack Jansen95839b82003-02-09 23:10:20 +0000358class PimpDatabase:
Jack Jansen0ae32202003-04-09 13:25:43 +0000359 """Class representing a pimp database. It can actually contain
360 information from multiple databases through inclusion, but the
361 toplevel database is considered the master, as its maintainer is
362 "responsible" for the contents."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000363
Jack Jansen0ae32202003-04-09 13:25:43 +0000364 def __init__(self, prefs):
365 self._packages = []
366 self.preferences = prefs
Jack Jansen118e1272004-03-13 23:50:48 +0000367 self._url = ""
Jack Jansen0ae32202003-04-09 13:25:43 +0000368 self._urllist = []
369 self._version = ""
370 self._maintainer = ""
371 self._description = ""
Tim Peters182b5ac2004-07-18 06:16:08 +0000372
Jack Jansen118e1272004-03-13 23:50:48 +0000373 # Accessor functions
374 def url(self): return self._url
375 def version(self): return self._version
376 def maintainer(self): return self._maintainer
377 def description(self): return self._description
Tim Peters182b5ac2004-07-18 06:16:08 +0000378
Jack Jansen0ae32202003-04-09 13:25:43 +0000379 def close(self):
380 """Clean up"""
381 self._packages = []
382 self.preferences = None
Tim Peters182b5ac2004-07-18 06:16:08 +0000383
Jack Jansen0ae32202003-04-09 13:25:43 +0000384 def appendURL(self, url, included=0):
385 """Append packages from the database with the given URL.
386 Only the first database should specify included=0, so the
387 global information (maintainer, description) get stored."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000388
Jack Jansen0ae32202003-04-09 13:25:43 +0000389 if url in self._urllist:
390 return
391 self._urllist.append(url)
392 fp = urllib2.urlopen(url).fp
Jack Jansenc32cec12004-03-13 23:32:47 +0000393 plistdata = plistlib.Plist.fromFile(fp)
Jack Jansen0ae32202003-04-09 13:25:43 +0000394 # Test here for Pimp version, etc
Jack Jansenb789a062003-05-28 18:56:30 +0000395 if included:
Jack Jansenc32cec12004-03-13 23:32:47 +0000396 version = plistdata.get('Version')
Jack Jansenb789a062003-05-28 18:56:30 +0000397 if version and version > self._version:
398 sys.stderr.write("Warning: included database %s is for pimp version %s\n" %
399 (url, version))
400 else:
Jack Jansenc32cec12004-03-13 23:32:47 +0000401 self._version = plistdata.get('Version')
Jack Jansenb789a062003-05-28 18:56:30 +0000402 if not self._version:
403 sys.stderr.write("Warning: database has no Version information\n")
404 elif self._version > PIMP_VERSION:
Tim Peters182b5ac2004-07-18 06:16:08 +0000405 sys.stderr.write("Warning: database version %s newer than pimp version %s\n"
Jack Jansen0ae32202003-04-09 13:25:43 +0000406 % (self._version, PIMP_VERSION))
Jack Jansenc32cec12004-03-13 23:32:47 +0000407 self._maintainer = plistdata.get('Maintainer', '')
408 self._description = plistdata.get('Description', '').strip()
Jack Jansen118e1272004-03-13 23:50:48 +0000409 self._url = url
Jack Jansen1dd087c2005-01-07 13:43:31 +0000410 self._appendPackages(plistdata['Packages'], url)
Jack Jansenc32cec12004-03-13 23:32:47 +0000411 others = plistdata.get('Include', [])
Jack Jansen1dd087c2005-01-07 13:43:31 +0000412 for o in others:
413 o = urllib.basejoin(url, o)
414 self.appendURL(o, included=1)
Tim Peters182b5ac2004-07-18 06:16:08 +0000415
Jack Jansen1dd087c2005-01-07 13:43:31 +0000416 def _appendPackages(self, packages, url):
Jack Jansen0ae32202003-04-09 13:25:43 +0000417 """Given a list of dictionaries containing package
418 descriptions create the PimpPackage objects and append them
419 to our internal storage."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000420
Jack Jansen0ae32202003-04-09 13:25:43 +0000421 for p in packages:
422 p = dict(p)
Mark Dickinsona3a50502010-04-03 18:17:54 +0000423 if 'Download-URL' in p:
Jack Jansen1dd087c2005-01-07 13:43:31 +0000424 p['Download-URL'] = urllib.basejoin(url, p['Download-URL'])
Jack Jansen0ae32202003-04-09 13:25:43 +0000425 flavor = p.get('Flavor')
426 if flavor == 'source':
427 pkg = PimpPackage_source(self, p)
428 elif flavor == 'binary':
429 pkg = PimpPackage_binary(self, p)
Jack Jansenaf304a62005-01-03 15:44:18 +0000430 elif flavor == 'installer':
431 pkg = PimpPackage_installer(self, p)
432 elif flavor == 'hidden':
433 pkg = PimpPackage_installer(self, p)
Jack Jansen0ae32202003-04-09 13:25:43 +0000434 else:
435 pkg = PimpPackage(self, dict(p))
436 self._packages.append(pkg)
Tim Peters182b5ac2004-07-18 06:16:08 +0000437
Jack Jansen0ae32202003-04-09 13:25:43 +0000438 def list(self):
439 """Return a list of all PimpPackage objects in the database."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000440
Jack Jansen0ae32202003-04-09 13:25:43 +0000441 return self._packages
Tim Peters182b5ac2004-07-18 06:16:08 +0000442
Jack Jansen0ae32202003-04-09 13:25:43 +0000443 def listnames(self):
444 """Return a list of names of all packages in the database."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000445
Jack Jansen0ae32202003-04-09 13:25:43 +0000446 rv = []
447 for pkg in self._packages:
448 rv.append(pkg.fullname())
449 rv.sort()
450 return rv
Tim Peters182b5ac2004-07-18 06:16:08 +0000451
Jack Jansen0ae32202003-04-09 13:25:43 +0000452 def dump(self, pathOrFile):
453 """Dump the contents of the database to an XML .plist file.
Tim Peters182b5ac2004-07-18 06:16:08 +0000454
Jack Jansen0ae32202003-04-09 13:25:43 +0000455 The file can be passed as either a file object or a pathname.
456 All data, including included databases, is dumped."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000457
Jack Jansen0ae32202003-04-09 13:25:43 +0000458 packages = []
459 for pkg in self._packages:
460 packages.append(pkg.dump())
Jack Jansenc32cec12004-03-13 23:32:47 +0000461 plistdata = {
Jack Jansen0ae32202003-04-09 13:25:43 +0000462 'Version': self._version,
463 'Maintainer': self._maintainer,
464 'Description': self._description,
465 'Packages': packages
466 }
Jack Jansenc32cec12004-03-13 23:32:47 +0000467 plist = plistlib.Plist(**plistdata)
Jack Jansen0ae32202003-04-09 13:25:43 +0000468 plist.write(pathOrFile)
Tim Peters182b5ac2004-07-18 06:16:08 +0000469
Jack Jansen0ae32202003-04-09 13:25:43 +0000470 def find(self, ident):
471 """Find a package. The package can be specified by name
472 or as a dictionary with name, version and flavor entries.
Tim Peters182b5ac2004-07-18 06:16:08 +0000473
Jack Jansen0ae32202003-04-09 13:25:43 +0000474 Only name is obligatory. If there are multiple matches the
475 best one (higher version number, flavors ordered according to
476 users' preference) is returned."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000477
Jack Jansen0ae32202003-04-09 13:25:43 +0000478 if type(ident) == str:
479 # Remove ( and ) for pseudo-packages
480 if ident[0] == '(' and ident[-1] == ')':
481 ident = ident[1:-1]
482 # Split into name-version-flavor
483 fields = ident.split('-')
484 if len(fields) < 1 or len(fields) > 3:
485 return None
486 name = fields[0]
487 if len(fields) > 1:
488 version = fields[1]
489 else:
490 version = None
491 if len(fields) > 2:
492 flavor = fields[2]
493 else:
494 flavor = None
495 else:
496 name = ident['Name']
497 version = ident.get('Version')
498 flavor = ident.get('Flavor')
499 found = None
500 for p in self._packages:
501 if name == p.name() and \
502 (not version or version == p.version()) and \
503 (not flavor or flavor == p.flavor()):
504 if not found or found < p:
505 found = p
506 return found
Tim Peters182b5ac2004-07-18 06:16:08 +0000507
Jack Jansene7b33db2003-02-11 22:40:59 +0000508ALLOWED_KEYS = [
Jack Jansen0ae32202003-04-09 13:25:43 +0000509 "Name",
510 "Version",
511 "Flavor",
512 "Description",
513 "Home-page",
514 "Download-URL",
515 "Install-test",
516 "Install-command",
517 "Pre-install-command",
518 "Post-install-command",
519 "Prerequisites",
Jack Jansen5da131b2003-06-01 20:57:12 +0000520 "MD5Sum",
521 "User-install-skips",
522 "Systemwide-only",
Jack Jansene7b33db2003-02-11 22:40:59 +0000523]
524
Jack Jansen95839b82003-02-09 23:10:20 +0000525class PimpPackage:
Jack Jansen0ae32202003-04-09 13:25:43 +0000526 """Class representing a single package."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000527
Jack Jansenc32cec12004-03-13 23:32:47 +0000528 def __init__(self, db, plistdata):
Jack Jansen0ae32202003-04-09 13:25:43 +0000529 self._db = db
Jack Jansenc32cec12004-03-13 23:32:47 +0000530 name = plistdata["Name"]
531 for k in plistdata.keys():
Jack Jansen0ae32202003-04-09 13:25:43 +0000532 if not k in ALLOWED_KEYS:
533 sys.stderr.write("Warning: %s: unknown key %s\n" % (name, k))
Jack Jansenc32cec12004-03-13 23:32:47 +0000534 self._dict = plistdata
Tim Peters182b5ac2004-07-18 06:16:08 +0000535
Jack Jansen0ae32202003-04-09 13:25:43 +0000536 def __getitem__(self, key):
537 return self._dict[key]
Tim Peters182b5ac2004-07-18 06:16:08 +0000538
Jack Jansen0ae32202003-04-09 13:25:43 +0000539 def name(self): return self._dict['Name']
Jack Jansenc7c78ae2003-05-06 13:07:32 +0000540 def version(self): return self._dict.get('Version')
541 def flavor(self): return self._dict.get('Flavor')
Jack Jansen9f0c5752003-05-29 22:07:27 +0000542 def description(self): return self._dict['Description'].strip()
Jack Jansen2a97dcc2003-06-01 20:03:43 +0000543 def shortdescription(self): return self.description().splitlines()[0]
Jack Jansen0ae32202003-04-09 13:25:43 +0000544 def homepage(self): return self._dict.get('Home-page')
Jack Jansenc7c78ae2003-05-06 13:07:32 +0000545 def downloadURL(self): return self._dict.get('Download-URL')
Jack Jansen5da131b2003-06-01 20:57:12 +0000546 def systemwideOnly(self): return self._dict.get('Systemwide-only')
Tim Peters182b5ac2004-07-18 06:16:08 +0000547
Jack Jansen0ae32202003-04-09 13:25:43 +0000548 def fullname(self):
549 """Return the full name "name-version-flavor" of a package.
Tim Peters182b5ac2004-07-18 06:16:08 +0000550
Jack Jansen0ae32202003-04-09 13:25:43 +0000551 If the package is a pseudo-package, something that cannot be
552 installed through pimp, return the name in (parentheses)."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000553
Jack Jansen0ae32202003-04-09 13:25:43 +0000554 rv = self._dict['Name']
Mark Dickinsona3a50502010-04-03 18:17:54 +0000555 if 'Version' in self._dict:
Jack Jansen0ae32202003-04-09 13:25:43 +0000556 rv = rv + '-%s' % self._dict['Version']
Mark Dickinsona3a50502010-04-03 18:17:54 +0000557 if 'Flavor' in self._dict:
Jack Jansen0ae32202003-04-09 13:25:43 +0000558 rv = rv + '-%s' % self._dict['Flavor']
Jack Jansenaf304a62005-01-03 15:44:18 +0000559 if self._dict.get('Flavor') == 'hidden':
Jack Jansen0ae32202003-04-09 13:25:43 +0000560 # Pseudo-package, show in parentheses
561 rv = '(%s)' % rv
562 return rv
Tim Peters182b5ac2004-07-18 06:16:08 +0000563
Jack Jansen0ae32202003-04-09 13:25:43 +0000564 def dump(self):
565 """Return a dict object containing the information on the package."""
566 return self._dict
Tim Peters182b5ac2004-07-18 06:16:08 +0000567
Jack Jansen0ae32202003-04-09 13:25:43 +0000568 def __cmp__(self, other):
569 """Compare two packages, where the "better" package sorts lower."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000570
Jack Jansen0ae32202003-04-09 13:25:43 +0000571 if not isinstance(other, PimpPackage):
572 return cmp(id(self), id(other))
573 if self.name() != other.name():
574 return cmp(self.name(), other.name())
575 if self.version() != other.version():
576 return -cmp(self.version(), other.version())
577 return self._db.preferences.compareFlavors(self.flavor(), other.flavor())
Tim Peters182b5ac2004-07-18 06:16:08 +0000578
Jack Jansen0ae32202003-04-09 13:25:43 +0000579 def installed(self):
580 """Test wheter the package is installed.
Tim Peters182b5ac2004-07-18 06:16:08 +0000581
Jack Jansen0ae32202003-04-09 13:25:43 +0000582 Returns two values: a status indicator which is one of
583 "yes", "no", "old" (an older version is installed) or "bad"
584 (something went wrong during the install test) and a human
585 readable string which may contain more details."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000586
Jack Jansen0ae32202003-04-09 13:25:43 +0000587 namespace = {
588 "NotInstalled": _scriptExc_NotInstalled,
589 "OldInstalled": _scriptExc_OldInstalled,
590 "BadInstalled": _scriptExc_BadInstalled,
591 "os": os,
592 "sys": sys,
593 }
594 installTest = self._dict['Install-test'].strip() + '\n'
595 try:
596 exec installTest in namespace
597 except ImportError, arg:
598 return "no", str(arg)
599 except _scriptExc_NotInstalled, arg:
600 return "no", str(arg)
601 except _scriptExc_OldInstalled, arg:
602 return "old", str(arg)
603 except _scriptExc_BadInstalled, arg:
604 return "bad", str(arg)
605 except:
606 sys.stderr.write("-------------------------------------\n")
607 sys.stderr.write("---- %s: install test got exception\n" % self.fullname())
608 sys.stderr.write("---- source:\n")
609 sys.stderr.write(installTest)
610 sys.stderr.write("---- exception:\n")
611 import traceback
612 traceback.print_exc(file=sys.stderr)
613 if self._db._maintainer:
614 sys.stderr.write("---- Please copy this and mail to %s\n" % self._db._maintainer)
615 sys.stderr.write("-------------------------------------\n")
616 return "bad", "Package install test got exception"
617 return "yes", ""
Tim Peters182b5ac2004-07-18 06:16:08 +0000618
Jack Jansen0ae32202003-04-09 13:25:43 +0000619 def prerequisites(self):
620 """Return a list of prerequisites for this package.
Tim Peters182b5ac2004-07-18 06:16:08 +0000621
Jack Jansen0ae32202003-04-09 13:25:43 +0000622 The list contains 2-tuples, of which the first item is either
623 a PimpPackage object or None, and the second is a descriptive
624 string. The first item can be None if this package depends on
625 something that isn't pimp-installable, in which case the descriptive
626 string should tell the user what to do."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000627
Jack Jansen0ae32202003-04-09 13:25:43 +0000628 rv = []
629 if not self._dict.get('Download-URL'):
Jack Jansen705553a2003-05-06 12:44:00 +0000630 # For pseudo-packages that are already installed we don't
631 # return an error message
632 status, _ = self.installed()
633 if status == "yes":
634 return []
Tim Peters182b5ac2004-07-18 06:16:08 +0000635 return [(None,
Jack Jansenaf304a62005-01-03 15:44:18 +0000636 "Package %s cannot be installed automatically, see the description" %
Jack Jansen0ae32202003-04-09 13:25:43 +0000637 self.fullname())]
Jack Jansen5da131b2003-06-01 20:57:12 +0000638 if self.systemwideOnly() and self._db.preferences.isUserInstall():
639 return [(None,
Jack Jansenaf304a62005-01-03 15:44:18 +0000640 "Package %s can only be installed system-wide" %
Jack Jansen5da131b2003-06-01 20:57:12 +0000641 self.fullname())]
Jack Jansen0ae32202003-04-09 13:25:43 +0000642 if not self._dict.get('Prerequisites'):
643 return []
644 for item in self._dict['Prerequisites']:
645 if type(item) == str:
646 pkg = None
647 descr = str(item)
648 else:
649 name = item['Name']
Mark Dickinsona3a50502010-04-03 18:17:54 +0000650 if 'Version' in item:
Jack Jansen0ae32202003-04-09 13:25:43 +0000651 name = name + '-' + item['Version']
Mark Dickinsona3a50502010-04-03 18:17:54 +0000652 if 'Flavor' in item:
Jack Jansen0ae32202003-04-09 13:25:43 +0000653 name = name + '-' + item['Flavor']
654 pkg = self._db.find(name)
655 if not pkg:
656 descr = "Requires unknown %s"%name
657 else:
Jack Jansen2a97dcc2003-06-01 20:03:43 +0000658 descr = pkg.shortdescription()
Jack Jansen0ae32202003-04-09 13:25:43 +0000659 rv.append((pkg, descr))
660 return rv
Tim Peters182b5ac2004-07-18 06:16:08 +0000661
662
Jack Jansen0ae32202003-04-09 13:25:43 +0000663 def downloadPackageOnly(self, output=None):
664 """Download a single package, if needed.
Tim Peters182b5ac2004-07-18 06:16:08 +0000665
Jack Jansen0ae32202003-04-09 13:25:43 +0000666 An MD5 signature is used to determine whether download is needed,
667 and to test that we actually downloaded what we expected.
668 If output is given it is a file-like object that will receive a log
669 of what happens.
Tim Peters182b5ac2004-07-18 06:16:08 +0000670
Jack Jansen0ae32202003-04-09 13:25:43 +0000671 If anything unforeseen happened the method returns an error message
672 string.
673 """
Tim Peters182b5ac2004-07-18 06:16:08 +0000674
Jack Jansen0ae32202003-04-09 13:25:43 +0000675 scheme, loc, path, query, frag = urlparse.urlsplit(self._dict['Download-URL'])
676 path = urllib.url2pathname(path)
677 filename = os.path.split(path)[1]
Tim Peters182b5ac2004-07-18 06:16:08 +0000678 self.archiveFilename = os.path.join(self._db.preferences.downloadDir, filename)
Jack Jansen0ae32202003-04-09 13:25:43 +0000679 if not self._archiveOK():
680 if scheme == 'manual':
681 return "Please download package manually and save as %s" % self.archiveFilename
Tim Peters182b5ac2004-07-18 06:16:08 +0000682 downloader = PimpUrllibDownloader(None, self._db.preferences.downloadDir,
Jack Jansen989ddc02004-03-11 23:03:59 +0000683 watcher=self._db.preferences.watcher)
684 if not downloader.download(self._dict['Download-URL'],
685 self.archiveFilename, output):
Jack Jansen0ae32202003-04-09 13:25:43 +0000686 return "download command failed"
687 if not os.path.exists(self.archiveFilename) and not NO_EXECUTE:
688 return "archive not found after download"
689 if not self._archiveOK():
690 return "archive does not have correct MD5 checksum"
Tim Peters182b5ac2004-07-18 06:16:08 +0000691
Jack Jansen0ae32202003-04-09 13:25:43 +0000692 def _archiveOK(self):
693 """Test an archive. It should exist and the MD5 checksum should be correct."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000694
Jack Jansen0ae32202003-04-09 13:25:43 +0000695 if not os.path.exists(self.archiveFilename):
696 return 0
697 if not self._dict.get('MD5Sum'):
698 sys.stderr.write("Warning: no MD5Sum for %s\n" % self.fullname())
699 return 1
700 data = open(self.archiveFilename, 'rb').read()
Georg Brandlbffb0bc2006-04-30 08:57:35 +0000701 checksum = hashlib.md5(data).hexdigest()
Jack Jansen0ae32202003-04-09 13:25:43 +0000702 return checksum == self._dict['MD5Sum']
Tim Peters182b5ac2004-07-18 06:16:08 +0000703
Jack Jansen0ae32202003-04-09 13:25:43 +0000704 def unpackPackageOnly(self, output=None):
705 """Unpack a downloaded package archive."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000706
Jack Jansen0ae32202003-04-09 13:25:43 +0000707 filename = os.path.split(self.archiveFilename)[1]
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000708 for ext, unpackerClass, arg in ARCHIVE_FORMATS:
Jack Jansen0ae32202003-04-09 13:25:43 +0000709 if filename[-len(ext):] == ext:
710 break
711 else:
712 return "unknown extension for archive file: %s" % filename
713 self.basename = filename[:-len(ext)]
Tim Peters182b5ac2004-07-18 06:16:08 +0000714 unpacker = unpackerClass(arg, dir=self._db.preferences.buildDir,
Jack Jansen989ddc02004-03-11 23:03:59 +0000715 watcher=self._db.preferences.watcher)
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000716 rv = unpacker.unpack(self.archiveFilename, output=output)
717 if rv:
718 return rv
Tim Peters182b5ac2004-07-18 06:16:08 +0000719
Jack Jansen0ae32202003-04-09 13:25:43 +0000720 def installPackageOnly(self, output=None):
721 """Default install method, to be overridden by subclasses"""
722 return "%s: This package needs to be installed manually (no support for flavor=\"%s\")" \
723 % (self.fullname(), self._dict.get(flavor, ""))
Tim Peters182b5ac2004-07-18 06:16:08 +0000724
Jack Jansen0ae32202003-04-09 13:25:43 +0000725 def installSinglePackage(self, output=None):
726 """Download, unpack and install a single package.
Tim Peters182b5ac2004-07-18 06:16:08 +0000727
Jack Jansen0ae32202003-04-09 13:25:43 +0000728 If output is given it should be a file-like object and it
729 will receive a log of what happened."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000730
Jack Jansen749f4812003-07-21 20:47:11 +0000731 if not self._dict.get('Download-URL'):
732 return "%s: This package needs to be installed manually (no Download-URL field)" % self.fullname()
Jack Jansen0ae32202003-04-09 13:25:43 +0000733 msg = self.downloadPackageOnly(output)
734 if msg:
735 return "%s: download: %s" % (self.fullname(), msg)
Tim Peters182b5ac2004-07-18 06:16:08 +0000736
Jack Jansen0ae32202003-04-09 13:25:43 +0000737 msg = self.unpackPackageOnly(output)
738 if msg:
739 return "%s: unpack: %s" % (self.fullname(), msg)
Tim Peters182b5ac2004-07-18 06:16:08 +0000740
Jack Jansen0ae32202003-04-09 13:25:43 +0000741 return self.installPackageOnly(output)
Tim Peters182b5ac2004-07-18 06:16:08 +0000742
Jack Jansen0ae32202003-04-09 13:25:43 +0000743 def beforeInstall(self):
744 """Bookkeeping before installation: remember what we have in site-packages"""
745 self._old_contents = os.listdir(self._db.preferences.installDir)
Tim Peters182b5ac2004-07-18 06:16:08 +0000746
Jack Jansen0ae32202003-04-09 13:25:43 +0000747 def afterInstall(self):
748 """Bookkeeping after installation: interpret any new .pth files that have
749 appeared"""
Tim Peters182b5ac2004-07-18 06:16:08 +0000750
Jack Jansen0ae32202003-04-09 13:25:43 +0000751 new_contents = os.listdir(self._db.preferences.installDir)
752 for fn in new_contents:
753 if fn in self._old_contents:
754 continue
755 if fn[-4:] != '.pth':
756 continue
757 fullname = os.path.join(self._db.preferences.installDir, fn)
758 f = open(fullname)
759 for line in f.readlines():
760 if not line:
761 continue
762 if line[0] == '#':
763 continue
764 if line[:6] == 'import':
765 exec line
766 continue
767 if line[-1] == '\n':
768 line = line[:-1]
769 if not os.path.isabs(line):
770 line = os.path.join(self._db.preferences.installDir, line)
771 line = os.path.realpath(line)
772 if not line in sys.path:
Tim Peters182b5ac2004-07-18 06:16:08 +0000773 sys.path.append(line)
Jack Jansen95839b82003-02-09 23:10:20 +0000774
Jack Jansen5da131b2003-06-01 20:57:12 +0000775 def filterExpectedSkips(self, names):
776 """Return a list that contains only unpexpected skips"""
777 if not self._db.preferences.isUserInstall():
778 return names
779 expected_skips = self._dict.get('User-install-skips')
780 if not expected_skips:
781 return names
782 newnames = []
783 for name in names:
784 for skip in expected_skips:
785 if name[:len(skip)] == skip:
786 break
787 else:
788 newnames.append(name)
789 return newnames
790
Jack Jansen0dacac42003-02-14 14:11:59 +0000791class PimpPackage_binary(PimpPackage):
792
Jack Jansen0ae32202003-04-09 13:25:43 +0000793 def unpackPackageOnly(self, output=None):
794 """We don't unpack binary packages until installing"""
795 pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000796
Jack Jansen0ae32202003-04-09 13:25:43 +0000797 def installPackageOnly(self, output=None):
798 """Install a single source package.
Tim Peters182b5ac2004-07-18 06:16:08 +0000799
Jack Jansen0ae32202003-04-09 13:25:43 +0000800 If output is given it should be a file-like object and it
801 will receive a log of what happened."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000802
Mark Dickinsona3a50502010-04-03 18:17:54 +0000803 if 'Install-command' in self._dict:
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000804 return "%s: Binary package cannot have Install-command" % self.fullname()
Tim Peters182b5ac2004-07-18 06:16:08 +0000805
Mark Dickinsona3a50502010-04-03 18:17:54 +0000806 if 'Pre-install-command' in self._dict:
Jack Jansenaf304a62005-01-03 15:44:18 +0000807 if _cmd(output, '/tmp', self._dict['Pre-install-command']):
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000808 return "pre-install %s: running \"%s\" failed" % \
809 (self.fullname(), self._dict['Pre-install-command'])
Tim Peters182b5ac2004-07-18 06:16:08 +0000810
Jack Jansen0ae32202003-04-09 13:25:43 +0000811 self.beforeInstall()
Jack Jansen0dacac42003-02-14 14:11:59 +0000812
Jack Jansen0ae32202003-04-09 13:25:43 +0000813 # Install by unpacking
814 filename = os.path.split(self.archiveFilename)[1]
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000815 for ext, unpackerClass, arg in ARCHIVE_FORMATS:
Jack Jansen0ae32202003-04-09 13:25:43 +0000816 if filename[-len(ext):] == ext:
817 break
818 else:
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000819 return "%s: unknown extension for archive file: %s" % (self.fullname(), filename)
820 self.basename = filename[:-len(ext)]
Tim Peters182b5ac2004-07-18 06:16:08 +0000821
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000822 install_renames = []
823 for k, newloc in self._db.preferences.installLocations:
824 if not newloc:
825 continue
826 if k == "--install-lib":
827 oldloc = DEFAULT_INSTALLDIR
828 else:
829 return "%s: Don't know installLocation %s" % (self.fullname(), k)
830 install_renames.append((oldloc, newloc))
Tim Peters182b5ac2004-07-18 06:16:08 +0000831
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000832 unpacker = unpackerClass(arg, dir="/", renames=install_renames)
Jack Jansen5da131b2003-06-01 20:57:12 +0000833 rv = unpacker.unpack(self.archiveFilename, output=output, package=self)
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000834 if rv:
835 return rv
Tim Peters182b5ac2004-07-18 06:16:08 +0000836
Jack Jansen0ae32202003-04-09 13:25:43 +0000837 self.afterInstall()
Tim Peters182b5ac2004-07-18 06:16:08 +0000838
Mark Dickinsona3a50502010-04-03 18:17:54 +0000839 if 'Post-install-command' in self._dict:
Jack Jansenaf304a62005-01-03 15:44:18 +0000840 if _cmd(output, '/tmp', self._dict['Post-install-command']):
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000841 return "%s: post-install: running \"%s\" failed" % \
Jack Jansen0ae32202003-04-09 13:25:43 +0000842 (self.fullname(), self._dict['Post-install-command'])
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000843
Jack Jansen0ae32202003-04-09 13:25:43 +0000844 return None
Tim Peters182b5ac2004-07-18 06:16:08 +0000845
846
Jack Jansen0dacac42003-02-14 14:11:59 +0000847class PimpPackage_source(PimpPackage):
848
Jack Jansen0ae32202003-04-09 13:25:43 +0000849 def unpackPackageOnly(self, output=None):
850 """Unpack a source package and check that setup.py exists"""
851 PimpPackage.unpackPackageOnly(self, output)
852 # Test that a setup script has been create
853 self._buildDirname = os.path.join(self._db.preferences.buildDir, self.basename)
854 setupname = os.path.join(self._buildDirname, "setup.py")
855 if not os.path.exists(setupname) and not NO_EXECUTE:
856 return "no setup.py found after unpack of archive"
Jack Jansen0dacac42003-02-14 14:11:59 +0000857
Jack Jansen0ae32202003-04-09 13:25:43 +0000858 def installPackageOnly(self, output=None):
859 """Install a single source package.
Tim Peters182b5ac2004-07-18 06:16:08 +0000860
Jack Jansen0ae32202003-04-09 13:25:43 +0000861 If output is given it should be a file-like object and it
862 will receive a log of what happened."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000863
Mark Dickinsona3a50502010-04-03 18:17:54 +0000864 if 'Pre-install-command' in self._dict:
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000865 if _cmd(output, self._buildDirname, self._dict['Pre-install-command']):
Jack Jansen0ae32202003-04-09 13:25:43 +0000866 return "pre-install %s: running \"%s\" failed" % \
867 (self.fullname(), self._dict['Pre-install-command'])
Tim Peters182b5ac2004-07-18 06:16:08 +0000868
Jack Jansen0ae32202003-04-09 13:25:43 +0000869 self.beforeInstall()
870 installcmd = self._dict.get('Install-command')
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000871 if installcmd and self._install_renames:
872 return "Package has install-command and can only be installed to standard location"
873 # This is the "bit-bucket" for installations: everything we don't
874 # want. After installation we check that it is actually empty
875 unwanted_install_dir = None
Jack Jansen0ae32202003-04-09 13:25:43 +0000876 if not installcmd:
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000877 extra_args = ""
878 for k, v in self._db.preferences.installLocations:
879 if not v:
880 # We don't want these files installed. Send them
881 # to the bit-bucket.
882 if not unwanted_install_dir:
883 unwanted_install_dir = tempfile.mkdtemp()
884 v = unwanted_install_dir
885 extra_args = extra_args + " %s \"%s\"" % (k, v)
886 installcmd = '"%s" setup.py install %s' % (sys.executable, extra_args)
887 if _cmd(output, self._buildDirname, installcmd):
Jack Jansen0ae32202003-04-09 13:25:43 +0000888 return "install %s: running \"%s\" failed" % \
889 (self.fullname(), installcmd)
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000890 if unwanted_install_dir and os.path.exists(unwanted_install_dir):
891 unwanted_files = os.listdir(unwanted_install_dir)
892 if unwanted_files:
893 rv = "Warning: some files were not installed: %s" % " ".join(unwanted_files)
894 else:
895 rv = None
896 shutil.rmtree(unwanted_install_dir)
897 return rv
Tim Peters182b5ac2004-07-18 06:16:08 +0000898
Jack Jansen0ae32202003-04-09 13:25:43 +0000899 self.afterInstall()
Tim Peters182b5ac2004-07-18 06:16:08 +0000900
Mark Dickinsona3a50502010-04-03 18:17:54 +0000901 if 'Post-install-command' in self._dict:
Jack Jansen6fde1ce2003-04-15 14:43:05 +0000902 if _cmd(output, self._buildDirname, self._dict['Post-install-command']):
Jack Jansen0ae32202003-04-09 13:25:43 +0000903 return "post-install %s: running \"%s\" failed" % \
904 (self.fullname(), self._dict['Post-install-command'])
905 return None
Tim Peters182b5ac2004-07-18 06:16:08 +0000906
Jack Jansenaf304a62005-01-03 15:44:18 +0000907class PimpPackage_installer(PimpPackage):
908
909 def unpackPackageOnly(self, output=None):
910 """We don't unpack dmg packages until installing"""
911 pass
912
913 def installPackageOnly(self, output=None):
914 """Install a single source package.
Tim Peters5a9fb3c2005-01-07 16:01:32 +0000915
Jack Jansenaf304a62005-01-03 15:44:18 +0000916 If output is given it should be a file-like object and it
917 will receive a log of what happened."""
Tim Peters5a9fb3c2005-01-07 16:01:32 +0000918
Mark Dickinsona3a50502010-04-03 18:17:54 +0000919 if 'Post-install-command' in self._dict:
Jack Jansenaf304a62005-01-03 15:44:18 +0000920 return "%s: Installer package cannot have Post-install-command" % self.fullname()
921
Mark Dickinsona3a50502010-04-03 18:17:54 +0000922 if 'Pre-install-command' in self._dict:
Jack Jansenaf304a62005-01-03 15:44:18 +0000923 if _cmd(output, '/tmp', self._dict['Pre-install-command']):
924 return "pre-install %s: running \"%s\" failed" % \
925 (self.fullname(), self._dict['Pre-install-command'])
Tim Peters5a9fb3c2005-01-07 16:01:32 +0000926
Jack Jansenaf304a62005-01-03 15:44:18 +0000927 self.beforeInstall()
928
929 installcmd = self._dict.get('Install-command')
930 if installcmd:
931 if '%' in installcmd:
932 installcmd = installcmd % self.archiveFilename
933 else:
Tim Peters5a9fb3c2005-01-07 16:01:32 +0000934 installcmd = 'open \"%s\"' % self.archiveFilename
Jack Jansenaf304a62005-01-03 15:44:18 +0000935 if _cmd(output, "/tmp", installcmd):
936 return '%s: install command failed (use verbose for details)' % self.fullname()
937 return '%s: downloaded and opened. Install manually and restart Package Manager' % self.archiveFilename
Tim Peters182b5ac2004-07-18 06:16:08 +0000938
Jack Jansen95839b82003-02-09 23:10:20 +0000939class PimpInstaller:
Jack Jansen0ae32202003-04-09 13:25:43 +0000940 """Installer engine: computes dependencies and installs
941 packages in the right order."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000942
Jack Jansen0ae32202003-04-09 13:25:43 +0000943 def __init__(self, db):
944 self._todo = []
945 self._db = db
946 self._curtodo = []
947 self._curmessages = []
Tim Peters182b5ac2004-07-18 06:16:08 +0000948
Jack Jansen0ae32202003-04-09 13:25:43 +0000949 def __contains__(self, package):
950 return package in self._todo
Tim Peters182b5ac2004-07-18 06:16:08 +0000951
Jack Jansen0ae32202003-04-09 13:25:43 +0000952 def _addPackages(self, packages):
953 for package in packages:
954 if not package in self._todo:
Jack Jansen118e1272004-03-13 23:50:48 +0000955 self._todo.append(package)
Tim Peters182b5ac2004-07-18 06:16:08 +0000956
Jack Jansen0ae32202003-04-09 13:25:43 +0000957 def _prepareInstall(self, package, force=0, recursive=1):
958 """Internal routine, recursive engine for prepareInstall.
Tim Peters182b5ac2004-07-18 06:16:08 +0000959
Jack Jansen0ae32202003-04-09 13:25:43 +0000960 Test whether the package is installed and (if not installed
961 or if force==1) prepend it to the temporary todo list and
962 call ourselves recursively on all prerequisites."""
Tim Peters182b5ac2004-07-18 06:16:08 +0000963
Jack Jansen0ae32202003-04-09 13:25:43 +0000964 if not force:
965 status, message = package.installed()
966 if status == "yes":
Tim Peters182b5ac2004-07-18 06:16:08 +0000967 return
Jack Jansen0ae32202003-04-09 13:25:43 +0000968 if package in self._todo or package in self._curtodo:
969 return
970 self._curtodo.insert(0, package)
971 if not recursive:
972 return
973 prereqs = package.prerequisites()
974 for pkg, descr in prereqs:
975 if pkg:
Jack Jansen0576d0a2004-03-13 23:03:38 +0000976 self._prepareInstall(pkg, False, recursive)
Jack Jansen0ae32202003-04-09 13:25:43 +0000977 else:
Jack Jansen20fa6752003-04-16 12:15:34 +0000978 self._curmessages.append("Problem with dependency: %s" % descr)
Tim Peters182b5ac2004-07-18 06:16:08 +0000979
Jack Jansen0ae32202003-04-09 13:25:43 +0000980 def prepareInstall(self, package, force=0, recursive=1):
981 """Prepare installation of a package.
Tim Peters182b5ac2004-07-18 06:16:08 +0000982
Jack Jansen0ae32202003-04-09 13:25:43 +0000983 If the package is already installed and force is false nothing
984 is done. If recursive is true prerequisites are installed first.
Tim Peters182b5ac2004-07-18 06:16:08 +0000985
Jack Jansen0ae32202003-04-09 13:25:43 +0000986 Returns a list of packages (to be passed to install) and a list
987 of messages of any problems encountered.
988 """
Tim Peters182b5ac2004-07-18 06:16:08 +0000989
Jack Jansen0ae32202003-04-09 13:25:43 +0000990 self._curtodo = []
991 self._curmessages = []
992 self._prepareInstall(package, force, recursive)
993 rv = self._curtodo, self._curmessages
994 self._curtodo = []
995 self._curmessages = []
996 return rv
Tim Peters182b5ac2004-07-18 06:16:08 +0000997
Jack Jansen0ae32202003-04-09 13:25:43 +0000998 def install(self, packages, output):
999 """Install a list of packages."""
Tim Peters182b5ac2004-07-18 06:16:08 +00001000
Jack Jansen0ae32202003-04-09 13:25:43 +00001001 self._addPackages(packages)
1002 status = []
1003 for pkg in self._todo:
1004 msg = pkg.installSinglePackage(output)
1005 if msg:
1006 status.append(msg)
1007 return status
Tim Peters182b5ac2004-07-18 06:16:08 +00001008
1009
1010
Jack Jansen989ddc02004-03-11 23:03:59 +00001011def _run(mode, verbose, force, args, prefargs, watcher):
Jack Jansen0ae32202003-04-09 13:25:43 +00001012 """Engine for the main program"""
Tim Peters182b5ac2004-07-18 06:16:08 +00001013
Jack Jansen6fde1ce2003-04-15 14:43:05 +00001014 prefs = PimpPreferences(**prefargs)
Jack Jansen989ddc02004-03-11 23:03:59 +00001015 if watcher:
1016 prefs.setWatcher(watcher)
Jack Jansen6fde1ce2003-04-15 14:43:05 +00001017 rv = prefs.check()
1018 if rv:
1019 sys.stdout.write(rv)
Jack Jansen0ae32202003-04-09 13:25:43 +00001020 db = PimpDatabase(prefs)
1021 db.appendURL(prefs.pimpDatabase)
Tim Peters182b5ac2004-07-18 06:16:08 +00001022
Jack Jansen0ae32202003-04-09 13:25:43 +00001023 if mode == 'dump':
1024 db.dump(sys.stdout)
1025 elif mode =='list':
1026 if not args:
1027 args = db.listnames()
1028 print "%-20.20s\t%s" % ("Package", "Description")
1029 print
1030 for pkgname in args:
1031 pkg = db.find(pkgname)
1032 if pkg:
Jack Jansen2a97dcc2003-06-01 20:03:43 +00001033 description = pkg.shortdescription()
Jack Jansen0ae32202003-04-09 13:25:43 +00001034 pkgname = pkg.fullname()
1035 else:
1036 description = 'Error: no such package'
1037 print "%-20.20s\t%s" % (pkgname, description)
1038 if verbose:
1039 print "\tHome page:\t", pkg.homepage()
Jack Jansen6fde1ce2003-04-15 14:43:05 +00001040 try:
1041 print "\tDownload URL:\t", pkg.downloadURL()
1042 except KeyError:
1043 pass
Jack Jansen9f0c5752003-05-29 22:07:27 +00001044 description = pkg.description()
Jack Jansen2a97dcc2003-06-01 20:03:43 +00001045 description = '\n\t\t\t\t\t'.join(description.splitlines())
Jack Jansen9f0c5752003-05-29 22:07:27 +00001046 print "\tDescription:\t%s" % description
Jack Jansen0ae32202003-04-09 13:25:43 +00001047 elif mode =='status':
1048 if not args:
1049 args = db.listnames()
1050 print "%-20.20s\t%s\t%s" % ("Package", "Installed", "Message")
1051 print
1052 for pkgname in args:
1053 pkg = db.find(pkgname)
1054 if pkg:
1055 status, msg = pkg.installed()
1056 pkgname = pkg.fullname()
1057 else:
1058 status = 'error'
1059 msg = 'No such package'
1060 print "%-20.20s\t%-9.9s\t%s" % (pkgname, status, msg)
1061 if verbose and status == "no":
1062 prereq = pkg.prerequisites()
1063 for pkg, msg in prereq:
1064 if not pkg:
1065 pkg = ''
1066 else:
1067 pkg = pkg.fullname()
1068 print "%-20.20s\tRequirement: %s %s" % ("", pkg, msg)
1069 elif mode == 'install':
1070 if not args:
1071 print 'Please specify packages to install'
1072 sys.exit(1)
1073 inst = PimpInstaller(db)
1074 for pkgname in args:
1075 pkg = db.find(pkgname)
1076 if not pkg:
1077 print '%s: No such package' % pkgname
1078 continue
1079 list, messages = inst.prepareInstall(pkg, force)
1080 if messages and not force:
1081 print "%s: Not installed:" % pkgname
1082 for m in messages:
1083 print "\t", m
1084 else:
1085 if verbose:
1086 output = sys.stdout
1087 else:
1088 output = None
1089 messages = inst.install(list, output)
1090 if messages:
1091 print "%s: Not installed:" % pkgname
1092 for m in messages:
1093 print "\t", m
Jack Jansen95839b82003-02-09 23:10:20 +00001094
1095def main():
Jack Jansen0ae32202003-04-09 13:25:43 +00001096 """Minimal commandline tool to drive pimp."""
Tim Peters182b5ac2004-07-18 06:16:08 +00001097
Jack Jansen0ae32202003-04-09 13:25:43 +00001098 import getopt
1099 def _help():
Jack Jansen6fde1ce2003-04-15 14:43:05 +00001100 print "Usage: pimp [options] -s [package ...] List installed status"
1101 print " pimp [options] -l [package ...] Show package information"
1102 print " pimp [options] -i package ... Install packages"
1103 print " pimp -d Dump database to stdout"
Jack Jansenb789a062003-05-28 18:56:30 +00001104 print " pimp -V Print version number"
Jack Jansen0ae32202003-04-09 13:25:43 +00001105 print "Options:"
Jack Jansen6fde1ce2003-04-15 14:43:05 +00001106 print " -v Verbose"
1107 print " -f Force installation"
Jack Jansenb789a062003-05-28 18:56:30 +00001108 print " -D dir Set destination directory"
1109 print " (default: %s)" % DEFAULT_INSTALLDIR
1110 print " -u url URL for database"
Jack Jansen0ae32202003-04-09 13:25:43 +00001111 sys.exit(1)
Tim Peters182b5ac2004-07-18 06:16:08 +00001112
Jack Jansen989ddc02004-03-11 23:03:59 +00001113 class _Watcher:
1114 def update(self, msg):
1115 sys.stderr.write(msg + '\r')
1116 return 1
Tim Peters182b5ac2004-07-18 06:16:08 +00001117
Jack Jansen0ae32202003-04-09 13:25:43 +00001118 try:
Jack Jansenb789a062003-05-28 18:56:30 +00001119 opts, args = getopt.getopt(sys.argv[1:], "slifvdD:Vu:")
1120 except getopt.GetoptError:
Jack Jansen0ae32202003-04-09 13:25:43 +00001121 _help()
1122 if not opts and not args:
1123 _help()
1124 mode = None
1125 force = 0
1126 verbose = 0
Jack Jansen6fde1ce2003-04-15 14:43:05 +00001127 prefargs = {}
Jack Jansen989ddc02004-03-11 23:03:59 +00001128 watcher = None
Jack Jansen0ae32202003-04-09 13:25:43 +00001129 for o, a in opts:
1130 if o == '-s':
1131 if mode:
1132 _help()
1133 mode = 'status'
1134 if o == '-l':
1135 if mode:
1136 _help()
1137 mode = 'list'
1138 if o == '-d':
1139 if mode:
1140 _help()
1141 mode = 'dump'
Jack Jansenb789a062003-05-28 18:56:30 +00001142 if o == '-V':
1143 if mode:
1144 _help()
1145 mode = 'version'
Jack Jansen0ae32202003-04-09 13:25:43 +00001146 if o == '-i':
1147 mode = 'install'
1148 if o == '-f':
1149 force = 1
1150 if o == '-v':
1151 verbose = 1
Jack Jansen989ddc02004-03-11 23:03:59 +00001152 watcher = _Watcher()
Jack Jansen6fde1ce2003-04-15 14:43:05 +00001153 if o == '-D':
1154 prefargs['installDir'] = a
Jack Jansenb789a062003-05-28 18:56:30 +00001155 if o == '-u':
1156 prefargs['pimpDatabase'] = a
Jack Jansen0ae32202003-04-09 13:25:43 +00001157 if not mode:
1158 _help()
Jack Jansenb789a062003-05-28 18:56:30 +00001159 if mode == 'version':
1160 print 'Pimp version %s; module name is %s' % (PIMP_VERSION, __name__)
1161 else:
Jack Jansen989ddc02004-03-11 23:03:59 +00001162 _run(mode, verbose, force, args, prefargs, watcher)
Jack Jansenb789a062003-05-28 18:56:30 +00001163
1164# Finally, try to update ourselves to a newer version.
1165# If the end-user updates pimp through pimp the new version
1166# will be called pimp_update and live in site-packages
1167# or somewhere similar
1168if __name__ != 'pimp_update':
1169 try:
1170 import pimp_update
1171 except ImportError:
1172 pass
1173 else:
1174 if pimp_update.PIMP_VERSION <= PIMP_VERSION:
1175 import warnings
1176 warnings.warn("pimp_update is version %s, not newer than pimp version %s" %
1177 (pimp_update.PIMP_VERSION, PIMP_VERSION))
1178 else:
1179 from pimp_update import *
Tim Peters182b5ac2004-07-18 06:16:08 +00001180
Jack Jansen95839b82003-02-09 23:10:20 +00001181if __name__ == '__main__':
Jack Jansen0ae32202003-04-09 13:25:43 +00001182 main()