aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/scripts/install.py
diff options
context:
space:
mode:
authorJacek Antonelli2008-09-06 18:24:57 -0500
committerJacek Antonelli2008-09-06 18:25:07 -0500
commit798d367d54a6c6379ad355bd8345fa40e31e7fe9 (patch)
tree1921f1708cd0240648c97bc02df2c2ab5f2fc41e /linden/scripts/install.py
parentSecond Life viewer sources 1.20.15 (diff)
downloadmeta-impy-798d367d54a6c6379ad355bd8345fa40e31e7fe9.zip
meta-impy-798d367d54a6c6379ad355bd8345fa40e31e7fe9.tar.gz
meta-impy-798d367d54a6c6379ad355bd8345fa40e31e7fe9.tar.bz2
meta-impy-798d367d54a6c6379ad355bd8345fa40e31e7fe9.tar.xz
Second Life viewer sources 1.21.0-RC
Diffstat (limited to 'linden/scripts/install.py')
-rwxr-xr-xlinden/scripts/install.py1086
1 files changed, 1086 insertions, 0 deletions
diff --git a/linden/scripts/install.py b/linden/scripts/install.py
new file mode 100755
index 0000000..0cc2fad
--- /dev/null
+++ b/linden/scripts/install.py
@@ -0,0 +1,1086 @@
1#!/usr/bin/env python
2"""\
3@file install.py
4@author Phoenix
5@date 2008-01-27
6@brief Install files into an indra checkout.
7
8Install files as specified by:
9https://wiki.lindenlab.com/wiki/User:Phoenix/Library_Installation
10
11
12$LicenseInfo:firstyear=2007&license=mit$
13
14Copyright (c) 2007-2008, Linden Research, Inc.
15
16Permission is hereby granted, free of charge, to any person obtaining a copy
17of this software and associated documentation files (the "Software"), to deal
18in the Software without restriction, including without limitation the rights
19to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20copies of the Software, and to permit persons to whom the Software is
21furnished to do so, subject to the following conditions:
22
23The above copyright notice and this permission notice shall be included in
24all copies or substantial portions of the Software.
25
26THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32THE SOFTWARE.
33$/LicenseInfo$
34"""
35
36import copy
37import md5
38import optparse
39import os
40import pprint
41import shutil
42import sys
43import tarfile
44import tempfile
45import urllib2
46import urlparse
47
48from sets import Set as set, ImmutableSet as frozenset
49
50# Locate -our- python library relative to our install location.
51from os.path import realpath, dirname, join
52
53# Walk back to checkout base directory
54base_dir = dirname(dirname(realpath(__file__)))
55# Walk in to libraries directory
56lib_dir = join(join(join(base_dir, 'indra'), 'lib'), 'python')
57
58if lib_dir not in sys.path:
59 sys.path.insert(0, lib_dir)
60
61from indra.base import llsd
62from indra.util import helpformatter
63
64class InstallFile(object):
65 "This is just a handy way to throw around details on a file in memory."
66 def __init__(self, pkgname, url, md5sum, cache_dir, platform_path):
67 self.pkgname = pkgname
68 self.url = url
69 self.md5sum = md5sum
70 filename = urlparse.urlparse(url)[2].split('/')[-1]
71 self.filename = os.path.join(cache_dir, filename)
72 self.platform_path = platform_path
73
74 def __str__(self):
75 return "ifile{%s:%s}" % (self.pkgname, self.url)
76
77 def _is_md5sum_match(self):
78 hasher = md5.new(file(self.filename, 'rb').read())
79 if hasher.hexdigest() == self.md5sum:
80 return True
81 return False
82
83 def is_match(self, platform):
84 """@brief Test to see if this ifile is part of platform
85 @param platform The target platform. Eg, windows or linux/i686/gcc/3.3
86 @return Returns True if the ifile is in the platform.
87 """
88 if self.platform_path[0] == 'common':
89 return True
90 req_platform_path = platform.split('/')
91 #print "platform:",req_platform_path
92 #print "path:",self.platform_path
93 # to match, every path part much match
94 match_count = min(len(req_platform_path), len(self.platform_path))
95 for ii in range(0, match_count):
96 if req_platform_path[ii] != self.platform_path[ii]:
97 return False
98 #print "match!"
99 return True
100
101 def fetch_local(self):
102 #print "Looking for:",self.filename
103 if not os.path.exists(self.filename):
104 pass
105 elif self.md5sum and not self._is_md5sum_match():
106 print "md5 mismatch:", self.filename
107 os.remove(self.filename)
108 else:
109 print "Found matching package:", self.filename
110 return
111 print "Downloading",self.url,"to local file",self.filename
112 file(self.filename, 'wb').write(urllib2.urlopen(self.url).read())
113 if self.md5sum and not self._is_md5sum_match():
114 raise RuntimeError("Error matching md5 for %s" % self.url)
115
116class LicenseDefinition(object):
117 def __init__(self, definition):
118 #probably looks like:
119 # { text : ...,
120 # url : ...
121 # blessed : ...
122 # }
123 self._definition = definition
124
125
126class InstallableDefinition(object):
127 def __init__(self, definition):
128 #probably looks like:
129 # { packages : {platform...},
130 # copyright : ...
131 # license : ...
132 # description: ...
133 # }
134 self._definition = definition
135
136 def _ifiles_from(self, tree, pkgname, cache_dir):
137 return self._ifiles_from_path(tree, pkgname, cache_dir, [])
138
139 def _ifiles_from_path(self, tree, pkgname, cache_dir, path):
140 ifiles = []
141 if 'url' in tree:
142 ifiles.append(InstallFile(
143 pkgname,
144 tree['url'],
145 tree.get('md5sum', None),
146 cache_dir,
147 path))
148 else:
149 for key in tree:
150 platform_path = copy.copy(path)
151 platform_path.append(key)
152 ifiles.extend(
153 self._ifiles_from_path(
154 tree[key],
155 pkgname,
156 cache_dir,
157 platform_path))
158 return ifiles
159
160 def ifiles(self, pkgname, platform, cache_dir):
161 """@brief return a list of appropriate InstallFile instances to install
162 @param pkgname The name of the package to be installed, eg 'tut'
163 @param platform The target platform. Eg, windows or linux/i686/gcc/3.3
164 @param cache_dir The directory to cache downloads.
165 @return Returns a list of InstallFiles which are part of this install
166 """
167 if 'packages' not in self._definition:
168 return []
169 all_ifiles = self._ifiles_from(
170 self._definition['packages'],
171 pkgname,
172 cache_dir)
173 if platform == 'all':
174 return all_ifiles
175 #print "Considering", len(all_ifiles), "packages for", pkgname
176 # split into 2 lines because pychecker thinks it might return none.
177 files = [ifile for ifile in all_ifiles if ifile.is_match(platform)]
178 return files
179
180class InstalledPackage(object):
181 def __init__(self, definition):
182 # looks like:
183 # { url1 : { files: [file1,file2,...], md5sum:... },
184 # url2 : { files: [file1,file2,...], md5sum:... },...
185 # }
186 self._installed = {}
187 for url in definition:
188 self._installed[url] = definition[url]
189
190 def urls(self):
191 return self._installed.keys()
192
193 def files_in(self, url):
194 return self._installed[url].get('files', [])
195
196 def get_md5sum(self, url):
197 return self._installed[url].get('md5sum', None)
198
199 def remove(self, url):
200 self._installed.pop(url)
201
202 def add_files(self, url, files):
203 if url not in self._installed:
204 self._installed[url] = {}
205 self._installed[url]['files'] = files
206
207 def set_md5sum(self, url, md5sum):
208 if url not in self._installed:
209 self._installed[url] = {}
210 self._installed[url]['md5sum'] = md5sum
211
212class Installer(object):
213 def __init__(self, install_filename, installed_filename, dryrun):
214 self._install_filename = install_filename
215 self._install_changed = False
216 self._installed_filename = installed_filename
217 self._installed_changed = False
218 self._dryrun = dryrun
219 self._installables = {}
220 self._licenses = {}
221 self._installed = {}
222 self.load()
223
224 def load(self):
225 if os.path.exists(self._install_filename):
226 install = llsd.parse(file(self._install_filename, 'rb').read())
227 try:
228 for name in install['installables']:
229 self._installables[name] = InstallableDefinition(
230 install['installables'][name])
231 except KeyError:
232 pass
233 try:
234 for name in install['licenses']:
235 self._licenses[name] = LicenseDefinition(install['licenses'][name])
236 except KeyError:
237 pass
238 if os.path.exists(self._installed_filename):
239 installed = llsd.parse(file(self._installed_filename, 'rb').read())
240 try:
241 bins = installed['installables']
242 for name in bins:
243 self._installed[name] = InstalledPackage(bins[name])
244 except KeyError:
245 pass
246
247 def _write(self, filename, state):
248 print "Writing state to",filename
249 if not self._dryrun:
250 file(filename, 'wb').write(llsd.format_pretty_xml(state))
251
252 def save(self):
253 if self._install_changed:
254 state = {}
255 state['licenses'] = {}
256 for name in self._licenses:
257 state['licenses'][name] = self._licenses[name]._definition
258 #print "self._installables:",self._installables
259 state['installables'] = {}
260 for name in self._installables:
261 state['installables'][name] = \
262 self._installables[name]._definition
263 self._write(self._install_filename, state)
264 if self._installed_changed:
265 state = {}
266 state['installables'] = {}
267 bin = state['installables']
268 for name in self._installed:
269 #print "installed:",name,self._installed[name]._installed
270 bin[name] = self._installed[name]._installed
271 self._write(self._installed_filename, state)
272
273 def is_valid_license(self, bin):
274 "@brief retrun true if we have valid license info for installable."
275 installable = self._installables[bin]._definition
276 if 'license' not in installable:
277 print >>sys.stderr, "No license info found for", bin
278 print >>sys.stderr, 'Please add the license with the',
279 print >>sys.stderr, '--add-installable option. See', \
280 sys.argv[0], '--help'
281 return False
282 if installable['license'] not in self._licenses:
283 lic = installable['license']
284 print >>sys.stderr, "Missing license info for '" + lic + "'.",
285 print >>sys.stderr, 'Please add the license with the',
286 print >>sys.stderr, '--add-license option. See', sys.argv[0],
287 print >>sys.stderr, '--help'
288 return False
289 return True
290
291 def list_installables(self):
292 "Return a list of all known installables."
293 return self._installables.keys()
294
295 def detail_installable(self, name):
296 "Return a installable definition detail"
297 return self._installables[name]._definition
298
299 def list_licenses(self):
300 "Return a list of all known licenses."
301 return self._licenses.keys()
302
303 def detail_license(self, name):
304 "Return a license definition detail"
305 return self._licenses[name]._definition
306
307 def list_installed(self):
308 "Return a list of installed packages."
309 return self._installed.keys()
310
311 def _update_field(self, description, field, value, multiline=False):
312 """Given a block and a field name, add or update it.
313 @param description a dict containing all the details of a description.
314 @param field the name of the field to update.
315 @param value the value of the field to update; if omitted, interview
316 will ask for value.
317 @param multiline boolean specifying whether field is multiline or not.
318 """
319 if value:
320 description[field] = value
321 else:
322 if field in description:
323 print "Update value for '" + field + "'"
324 print "(Leave blank to keep current value)"
325 print "Current Value: '" + description[field] + "'"
326 else:
327 print "Specify value for '" + field + "'"
328 if not multiline:
329 new_value = raw_input("Enter New Value: ")
330 else:
331 print "Please enter " + field + ". End input with EOF (^D)."
332 new_value = sys.stdin.read()
333
334 if field in description and not new_value:
335 pass
336 elif new_value:
337 description[field] = new_value
338
339 self._install_changed = True
340 return True
341
342 def _update_installable(self, name, platform, url, md5sum):
343 """Update installable entry with specific package information.
344 @param installable[in,out] a dict containing installable details.
345 @param platform Platform info, i.e. linux/i686, windows/i686 etc.
346 @param url URL of tar file
347 @param md5sum md5sum of tar file
348 """
349 installable = self._installables[name]._definition
350 path = platform.split('/')
351 if 'packages' not in installable:
352 installable['packages'] = {}
353 update = installable['packages']
354 for child in path:
355 if child not in update:
356 update[child] = {}
357 parent = update
358 update = update[child]
359 parent[child]['url'] = llsd.uri(url)
360 parent[child]['md5sum'] = md5sum
361
362 self._install_changed = True
363 return True
364
365
366 def add_installable_package(self, name, **kwargs):
367 """Add an url for a platform path to the installable.
368 @param installable[in,out] a dict containing installable details.
369 """
370 platform_help_str = """\
371Please enter a new package location and url. Some examples:
372common -- specify a package for all platforms
373linux -- specify a package for all arch and compilers on linux
374darwin/universal -- specify a mac os x universal
375windows/i686/vs/2003 -- specify a windows visual studio 2003 package"""
376 if name not in self._installables:
377 print "Error: must add library with --add-installable or " \
378 +"--add-installable-metadata before using " \
379 +"--add-installable-package option"
380 return False
381 else:
382 print "Updating installable '" + name + "'."
383 for arg in ('platform', 'url', 'md5sum'):
384 if not kwargs[arg]:
385 if arg == 'platform':
386 print platform_help_str
387 kwargs[arg] = raw_input("Package "+arg+":")
388 #path = kwargs['platform'].split('/')
389
390 return self._update_installable(name, kwargs['platform'],
391 kwargs['url'], kwargs['md5sum'])
392
393 def add_installable_metadata(self, name, **kwargs):
394 """Interactively add (only) library metadata into install,
395 w/o adding installable"""
396 if name not in self._installables:
397 print "Adding installable '" + name + "'."
398 self._installables[name] = InstallableDefinition({})
399 else:
400 print "Updating installable '" + name + "'."
401 installable = self._installables[name]._definition
402 for field in ('copyright', 'license', 'description'):
403 self._update_field(installable, field, kwargs[field])
404 print "Added installable '" + name + "':"
405 pprint.pprint(self._installables[name])
406
407 return True
408
409 def add_installable(self, name, **kwargs):
410 "Interactively pull a new installable into the install"
411 ret_a = self.add_installable_metadata(name, **kwargs)
412 ret_b = self.add_installable_package(name, **kwargs)
413 return (ret_a and ret_b)
414
415 def remove_installable(self, name):
416 self._installables.pop(name)
417 self._install_changed = True
418
419 def add_license(self, name, **kwargs):
420 if name not in self._licenses:
421 print "Adding license '" + name + "'."
422 self._licenses[name] = LicenseDefinition({})
423 else:
424 print "Updating license '" + name + "'."
425 the_license = self._licenses[name]._definition
426 for field in ('url', 'text'):
427 multiline = False
428 if field == 'text':
429 multiline = True
430 self._update_field(the_license, field, kwargs[field], multiline)
431 self._install_changed = True
432 return True
433
434 def remove_license(self, name):
435 self._licenses.pop(name)
436 self._install_changed = True
437
438 def _uninstall(self, installables):
439 """@brief Do the actual removal of files work.
440 *NOTE: This method is not transactionally safe -- ie, if it
441 raises an exception, internal state may be inconsistent. How
442 should we address this?
443 @param installables The package names to remove
444 """
445 remove_file_list = []
446 for pkgname in installables:
447 for url in self._installed[pkgname].urls():
448 remove_file_list.extend(
449 self._installed[pkgname].files_in(url))
450 self._installed[pkgname].remove(url)
451 if not self._dryrun:
452 self._installed_changed = True
453 if not self._dryrun:
454 self._installed.pop(pkgname)
455 remove_dir_set = set()
456 for filename in remove_file_list:
457 print "rm",filename
458 if not self._dryrun:
459 if os.path.exists(filename):
460 remove_dir_set.add(os.path.dirname(filename))
461 os.remove(filename)
462 for dirname in remove_dir_set:
463 try:
464 os.removedirs(dirname)
465 except OSError:
466 # This is just for cleanup, so we don't care about
467 # normal failures.
468 pass
469
470 def uninstall(self, installables, install_dir):
471 """@brief Remove the packages specified.
472 @param installables The package names to remove
473 @param install_dir The directory to work from
474 """
475 print "uninstall",installables,"from",install_dir
476 cwd = os.getcwdu()
477 os.chdir(install_dir)
478 try:
479 self._uninstall(installables)
480 finally:
481 os.chdir(cwd)
482
483 def _build_ifiles(self, platform, cache_dir):
484 """@brief determine what files to install
485 @param platform The target platform. Eg, windows or linux/i686/gcc/3.3
486 @param cache_dir The directory to cache downloads.
487 @return Returns the ifiles to install
488 """
489 ifiles = []
490 for bin in self._installables:
491 ifiles.extend(self._installables[bin].ifiles(bin,
492 platform,
493 cache_dir))
494 to_install = []
495 #print "self._installed",self._installed
496 for ifile in ifiles:
497 if ifile.pkgname not in self._installed:
498 to_install.append(ifile)
499 elif ifile.url not in self._installed[ifile.pkgname].urls():
500 to_install.append(ifile)
501 elif ifile.md5sum != \
502 self._installed[ifile.pkgname].get_md5sum(ifile.url):
503 # *TODO: We may want to uninstall the old version too
504 # when we detect it is installed, but the md5 sum is
505 # different.
506 to_install.append(ifile)
507 else:
508 #print "Installation up to date:",
509 # ifile.pkgname,ifile.platform_path
510 pass
511 #print "to_install",to_install
512 return to_install
513
514 def _install(self, to_install, install_dir):
515 for ifile in to_install:
516 tar = tarfile.open(ifile.filename, 'r')
517 print "Extracting",ifile.filename,"to",install_dir
518 if not self._dryrun:
519 # *NOTE: try to call extractall, which first appears
520 # in python 2.5. Phoenix 2008-01-28
521 try:
522 tar.extractall(path=install_dir)
523 except AttributeError:
524 _extractall(tar, path=install_dir)
525 if ifile.pkgname in self._installed:
526 self._installed[ifile.pkgname].add_files(
527 ifile.url,
528 tar.getnames())
529 self._installed[ifile.pkgname].set_md5sum(
530 ifile.url,
531 ifile.md5sum)
532 else:
533 # *HACK: this understands the installed package syntax.
534 definition = { ifile.url :
535 {'files': tar.getnames(),
536 'md5sum' : ifile.md5sum } }
537 self._installed[ifile.pkgname] = InstalledPackage(definition)
538 self._installed_changed = True
539
540 def install(self, installables, platform, install_dir, cache_dir):
541 """@brief Do the installation for for the platform.
542 @param installables The requested installables to install.
543 @param platform The target platform. Eg, windows or linux/i686/gcc/3.3
544 @param install_dir The root directory to install into. Created
545 if missing.
546 @param cache_dir The directory to cache downloads. Created if
547 missing.
548 """
549 # The ordering of steps in the method is to help reduce the
550 # likelihood that we break something.
551 install_dir = os.path.realpath(install_dir)
552 cache_dir = os.path.realpath(cache_dir)
553 _mkdir(install_dir)
554 _mkdir(cache_dir)
555 to_install = self._build_ifiles(platform, cache_dir)
556
557 # Filter for files which we actually requested to install.
558 to_install = [ifl for ifl in to_install if ifl.pkgname in installables]
559 for ifile in to_install:
560 ifile.fetch_local()
561 self._install(to_install, install_dir)
562
563 def do_install(self, installables, platform, install_dir, cache_dir=None,
564 check_license=True, scp=None):
565 """Determine what installables should be installed. If they were
566 passed in on the command line, use them, otherwise install
567 all known installables.
568 """
569 if not cache_dir:
570 cache_dir = _default_installable_cache()
571 all_installables = self.list_installables()
572 if not len(installables):
573 install_installables = all_installables
574 else:
575 # passed in on the command line. We'll need to verify we
576 # know about them here.
577 install_installables = installables
578 for installable in install_installables:
579 if installable not in all_installables:
580 raise RuntimeError('Unknown installable: %s' %
581 (installable,))
582 if check_license:
583 # *TODO: check against a list of 'known good' licenses.
584 # *TODO: check for urls which conflict -- will lead to
585 # problems.
586 for installable in install_installables:
587 if not self.is_valid_license(installable):
588 return 1
589
590 # Set up the 'scp' handler
591 opener = urllib2.build_opener()
592 scp_or_http = SCPOrHTTPHandler(scp)
593 opener.add_handler(scp_or_http)
594 urllib2.install_opener(opener)
595
596 # Do the work of installing the requested installables.
597 self.install(
598 install_installables,
599 platform,
600 install_dir,
601 cache_dir)
602 scp_or_http.cleanup()
603
604 def do_uninstall(self, installables, install_dir):
605 # Do not bother to check license if we're uninstalling.
606 all_installed = self.list_installed()
607 if not len(installables):
608 uninstall_installables = all_installed
609 else:
610 # passed in on the command line. We'll need to verify we
611 # know about them here.
612 uninstall_installables = installables
613 for installable in uninstall_installables:
614 if installable not in all_installed:
615 raise RuntimeError('Installable not installed: %s' %
616 (installable,))
617 self.uninstall(uninstall_installables, install_dir)
618
619class SCPOrHTTPHandler(urllib2.BaseHandler):
620 """Evil hack to allow both the build system and developers consume
621 proprietary binaries.
622 To use http, export the environment variable:
623 INSTALL_USE_HTTP_FOR_SCP=true
624 """
625 def __init__(self, scp_binary):
626 self._scp = scp_binary
627 self._dir = None
628
629 def scp_open(self, request):
630 #scp:codex.lindenlab.com:/local/share/install_pkgs/package.tar.bz2
631 remote = request.get_full_url()[4:]
632 if os.getenv('INSTALL_USE_HTTP_FOR_SCP', None) == 'true':
633 return self.do_http(remote)
634 try:
635 return self.do_scp(remote)
636 except:
637 self.cleanup()
638 raise
639
640 def do_http(self, remote):
641 url = remote.split(':',1)
642 if not url[1].startswith('/'):
643 # in case it's in a homedir or something
644 url.insert(1, '/')
645 url.insert(0, "http://")
646 url = ''.join(url)
647 print "Using HTTP:",url
648 return urllib2.urlopen(url)
649
650 def do_scp(self, remote):
651 if not self._dir:
652 self._dir = tempfile.mkdtemp()
653 local = os.path.join(self._dir, remote.split('/')[-1:][0])
654 command = []
655 for part in (self._scp, remote, local):
656 if ' ' in part:
657 # I hate shell escaping.
658 part.replace('\\', '\\\\')
659 part.replace('"', '\\"')
660 command.append('"%s"' % part)
661 else:
662 command.append(part)
663 #print "forking:", command
664 rv = os.system(' '.join(command))
665 if rv != 0:
666 raise RuntimeError("Cannot fetch: %s" % remote)
667 return file(local, 'rb')
668
669 def cleanup(self):
670 if self._dir:
671 shutil.rmtree(self._dir)
672
673
674#
675# *NOTE: PULLED FROM PYTHON 2.5 tarfile.py Phoenix 2008-01-28
676#
677def _extractall(tar, path=".", members=None):
678 """Extract all members from the archive to the current working
679 directory and set owner, modification time and permissions on
680 directories afterwards. `path' specifies a different directory
681 to extract to. `members' is optional and must be a subset of the
682 list returned by getmembers().
683 """
684 directories = []
685
686 if members is None:
687 members = tar
688
689 for tarinfo in members:
690 if tarinfo.isdir():
691 # Extract directory with a safe mode, so that
692 # all files below can be extracted as well.
693 try:
694 os.makedirs(os.path.join(path, tarinfo.name), 0777)
695 except EnvironmentError:
696 pass
697 directories.append(tarinfo)
698 else:
699 tar.extract(tarinfo, path)
700
701 # Reverse sort directories.
702 directories.sort(lambda a, b: cmp(a.name, b.name))
703 directories.reverse()
704
705 # Set correct owner, mtime and filemode on directories.
706 for tarinfo in directories:
707 path = os.path.join(path, tarinfo.name)
708 try:
709 tar.chown(tarinfo, path)
710 tar.utime(tarinfo, path)
711 tar.chmod(tarinfo, path)
712 except tarfile.ExtractError, e:
713 if tar.errorlevel > 1:
714 raise
715 else:
716 tar._dbg(1, "tarfile: %s" % e)
717
718
719def _mkdir(directory):
720 "Safe, repeatable way to make a directory."
721 if not os.path.exists(directory):
722 os.makedirs(directory)
723
724def _get_platform():
725 "Return appropriate platform packages for the environment."
726 platform_map = {
727 'darwin': 'darwin',
728 'linux2': 'linux',
729 'win32' : 'windows',
730 'cygwin' : 'windows',
731 'solaris' : 'solaris'
732 }
733 return platform_map[sys.platform]
734
735def _getuser():
736 "Get the user"
737 try:
738 # Unix-only.
739 import getpass
740 return getpass.getuser()
741 except ImportError:
742 import win32api
743 return win32api.GetUserName()
744
745def _default_installable_cache():
746 """In general, the installable files do not change much, so find a
747 host/user specific location to cache files."""
748 user = _getuser()
749 cache_dir = "/var/tmp/%s/install.cache" % user
750 if _get_platform() == 'windows':
751 cache_dir = os.path.join(tempfile.gettempdir(), \
752 'install.cache.%s' % user)
753 return cache_dir
754
755def parse_args():
756 parser = optparse.OptionParser(
757 usage="usage: %prog [options] [installable1 [installable2...]]",
758 formatter = helpformatter.Formatter(),
759 description="""This script fetches and installs installable packages.
760It also handles uninstalling those packages and manages the mapping between
761packages and their license.
762
763The process is to open and read an install manifest file which specifies
764what files should be installed. For each installable to be installed.
765 * make sure it has a license
766 * check the installed version
767 ** if not installed and needs to be, download and install
768 ** if installed version differs, download & install
769
770If no installables are specified on the command line, then the defaut
771behavior is to install all known installables appropriate for the platform
772specified or uninstall all installables if --uninstall is set. You can specify
773more than one installable on the command line.
774
775When specifying a platform, you can specify 'all' to install all
776packages, or any platform of the form:
777
778OS[/arch[/compiler[/compiler_version]]]
779
780Where the supported values for each are:
781OS: darwin, linux, windows, solaris
782arch: i686, x86_64, ppc, universal
783compiler: vs, gcc
784compiler_version: 2003, 2005, 2008, 3.3, 3.4, 4.0, etc.
785
786No checks are made to ensure a valid combination of platform
787parts. Some exmples of valid platforms:
788
789windows
790windows/i686/vs/2005
791linux/x86_64/gcc/3.3
792linux/x86_64/gcc/4.0
793darwin/universal/gcc/4.0
794""")
795 parser.add_option(
796 '--dry-run',
797 action='store_true',
798 default=False,
799 dest='dryrun',
800 help='Do not actually install files. Downloads will still happen.')
801 parser.add_option(
802 '--install-manifest',
803 type='string',
804 default=join(base_dir, 'install.xml'),
805 dest='install_filename',
806 help='The file used to describe what should be installed.')
807 parser.add_option(
808 '--installed-manifest',
809 type='string',
810 default=join(base_dir, 'installed.xml'),
811 dest='installed_filename',
812 help='The file used to record what is installed.')
813 parser.add_option(
814 '--export-manifest',
815 action='store_true',
816 default=False,
817 dest='export_manifest',
818 help="Print the install manifest to stdout and exit.")
819 parser.add_option(
820 '-p', '--platform',
821 type='string',
822 default=_get_platform(),
823 dest='platform',
824 help="""Override the automatically determined platform. \
825You can specify 'all' to do a installation of installables for all platforms.""")
826 parser.add_option(
827 '--cache-dir',
828 type='string',
829 default=_default_installable_cache(),
830 dest='cache_dir',
831 help='Where to download files. Default: %s'% \
832 (_default_installable_cache()))
833 parser.add_option(
834 '--install-dir',
835 type='string',
836 default=base_dir,
837 dest='install_dir',
838 help='Where to unpack the installed files.')
839 parser.add_option(
840 '--list-installed',
841 action='store_true',
842 default=False,
843 dest='list_installed',
844 help="List the installed package names and exit.")
845 parser.add_option(
846 '--skip-license-check',
847 action='store_false',
848 default=True,
849 dest='check_license',
850 help="Do not perform the license check.")
851 parser.add_option(
852 '--list-licenses',
853 action='store_true',
854 default=False,
855 dest='list_licenses',
856 help="List known licenses and exit.")
857 parser.add_option(
858 '--detail-license',
859 type='string',
860 default=None,
861 dest='detail_license',
862 help="Get detailed information on specified license and exit.")
863 parser.add_option(
864 '--add-license',
865 type='string',
866 default=None,
867 dest='new_license',
868 help="""Add a license to the install file. Argument is the name of \
869license. Specify --license-url if the license is remote or specify \
870--license-text, otherwse the license text will be read from standard \
871input.""")
872 parser.add_option(
873 '--license-url',
874 type='string',
875 default=None,
876 dest='license_url',
877 help="""Put the specified url into an added license. \
878Ignored if --add-license is not specified.""")
879 parser.add_option(
880 '--license-text',
881 type='string',
882 default=None,
883 dest='license_text',
884 help="""Put the text into an added license. \
885Ignored if --add-license is not specified.""")
886 parser.add_option(
887 '--remove-license',
888 type='string',
889 default=None,
890 dest='remove_license',
891 help="Remove a named license.")
892 parser.add_option(
893 '--remove-installable',
894 type='string',
895 default=None,
896 dest='remove_installable',
897 help="Remove a installable from the install file.")
898 parser.add_option(
899 '--add-installable',
900 type='string',
901 default=None,
902 dest='add_installable',
903 help="""Add a installable into the install file. Argument is \
904the name of the installable to add.""")
905 parser.add_option(
906 '--add-installable-metadata',
907 type='string',
908 default=None,
909 dest='add_installable_metadata',
910 help="""Add package for library into the install file. Argument is \
911the name of the library to add.""")
912 parser.add_option(
913 '--installable-copyright',
914 type='string',
915 default=None,
916 dest='installable_copyright',
917 help="""Copyright for specified new package. Ignored if \
918--add-installable is not specified.""")
919 parser.add_option(
920 '--installable-license',
921 type='string',
922 default=None,
923 dest='installable_license',
924 help="""Name of license for specified new package. Ignored if \
925--add-installable is not specified.""")
926 parser.add_option(
927 '--installable-description',
928 type='string',
929 default=None,
930 dest='installable_description',
931 help="""Description for specified new package. Ignored if \
932--add-installable is not specified.""")
933 parser.add_option(
934 '--add-installable-package',
935 type='string',
936 default=None,
937 dest='add_installable_package',
938 help="""Add package for library into the install file. Argument is \
939the name of the library to add.""")
940 parser.add_option(
941 '--package-platform',
942 type='string',
943 default=None,
944 dest='package_platform',
945 help="""Platform for specified new package. \
946Ignored if --add-installable or --add-installable-package is not specified.""")
947 parser.add_option(
948 '--package-url',
949 type='string',
950 default=None,
951 dest='package_url',
952 help="""URL for specified package. \
953Ignored if --add-installable or --add-installable-package is not specified.""")
954 parser.add_option(
955 '--package-md5',
956 type='string',
957 default=None,
958 dest='package_md5',
959 help="""md5sum for new package. \
960Ignored if --add-installable or --add-installable-package is not specified.""")
961 parser.add_option(
962 '--list',
963 action='store_true',
964 default=False,
965 dest='list_installables',
966 help="List the installables in the install manifest and exit.")
967 parser.add_option(
968 '--detail',
969 type='string',
970 default=None,
971 dest='detail_installable',
972 help="Get detailed information on specified installable and exit.")
973 parser.add_option(
974 '--uninstall',
975 action='store_true',
976 default=False,
977 dest='uninstall',
978 help="""Remove the installables specified in the arguments. Just like \
979during installation, if no installables are listed then all installed \
980installables are removed.""")
981 parser.add_option(
982 '--scp',
983 type='string',
984 default='scp',
985 dest='scp',
986 help="Specify the path to your scp program.")
987
988 return parser.parse_args()
989
990def main():
991 options, args = parse_args()
992 installer = Installer(
993 options.install_filename,
994 options.installed_filename,
995 options.dryrun)
996
997 #
998 # Handle the queries for information
999 #
1000 if options.list_installed:
1001 print "installed list:", installer.list_installed()
1002 return 0
1003 if options.list_installables:
1004 print "installable list:", installer.list_installables()
1005 return 0
1006 if options.detail_installable:
1007 try:
1008 detail = installer.detail_installable(options.detail_installable)
1009 print "Detail on installable",options.detail_installable+":"
1010 pprint.pprint(detail)
1011 except KeyError:
1012 print "Installable '"+options.detail_installable+"' not found in",
1013 print "install file."
1014 return 0
1015 if options.list_licenses:
1016 print "license list:", installer.list_licenses()
1017 return 0
1018 if options.detail_license:
1019 try:
1020 detail = installer.detail_license(options.detail_license)
1021 print "Detail on license",options.detail_license+":"
1022 pprint.pprint(detail)
1023 except KeyError:
1024 print "License '"+options.detail_license+"' not defined in",
1025 print "install file."
1026 return 0
1027 if options.export_manifest:
1028 # *HACK: just re-parse the install manifest and pretty print
1029 # it. easier than looking at the datastructure designed for
1030 # actually determining what to install
1031 install = llsd.parse(file(options.install_filename, 'rb').read())
1032 pprint.pprint(install)
1033 return 0
1034
1035 #
1036 # Handle updates -- can only do one of these
1037 # *TODO: should this change the command line syntax?
1038 #
1039 if options.new_license:
1040 if not installer.add_license(
1041 options.new_license,
1042 text=options.license_text,
1043 url=options.license_url):
1044 return 1
1045 elif options.remove_license:
1046 installer.remove_license(options.remove_license)
1047 elif options.remove_installable:
1048 installer.remove_installable(options.remove_installable)
1049 elif options.add_installable:
1050 if not installer.add_installable(
1051 options.add_installable,
1052 copyright=options.installable_copyright,
1053 license=options.installable_license,
1054 description=options.installable_description,
1055 platform=options.package_platform,
1056 url=options.package_url,
1057 md5sum=options.package_md5):
1058 return 1
1059 elif options.add_installable_metadata:
1060 if not installer.add_installable_metadata(
1061 options.add_installable_metadata,
1062 copyright=options.installable_copyright,
1063 license=options.installable_license,
1064 description=options.installable_description):
1065 return 1
1066 elif options.add_installable_package:
1067 if not installer.add_installable_package(
1068 options.add_installable_package,
1069 platform=options.package_platform,
1070 url=options.package_url,
1071 md5sum=options.package_md5):
1072 return 1
1073 elif options.uninstall:
1074 installer.do_uninstall(args, options.install_dir)
1075 else:
1076 installer.do_install(args, options.platform, options.install_dir,
1077 options.cache_dir, options.check_license,
1078 options.scp)
1079
1080 # save out any changes
1081 installer.save()
1082 return 0
1083
1084if __name__ == '__main__':
1085 #print sys.argv
1086 sys.exit(main())