install.py
上传用户:king477883
上传日期:2021-03-01
资源大小:9553k
文件大小:42k
源码类别:

游戏引擎

开发平台:

C++ Builder

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