summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjamesren <jamesren@592f7852-d20e-0410-864c-8624ca9c26a4>2010-02-12 00:46:40 +0000
committerjamesren <jamesren@592f7852-d20e-0410-864c-8624ca9c26a4>2010-02-12 00:46:40 +0000
commit88cfcc1d46f79b7bba603f70fbb21eb74182a87f (patch)
tree5df7cf4b9a2b8813c360f452733cc31a6bb6efea
parent0bf6e7a186a12d5fbe1c4fd73be2908257c9bc1c (diff)
Refactored build_externals to support site-specific packages. Package declarations are now in external_packages.py and site_external_packages.py; build_externals.py is just the base script.
Also added functions to support building packages using make. Signed-off-by: Steve Howard <showard@google.com> git-svn-id: svn://test.kernel.org/autotest/trunk@4238 592f7852-d20e-0410-864c-8624ca9c26a4
-rwxr-xr-xutils/build_externals.py680
-rw-r--r--utils/external_packages.py724
2 files changed, 733 insertions, 671 deletions
diff --git a/utils/build_externals.py b/utils/build_externals.py
index aea88791..268eadfc 100755
--- a/utils/build_externals.py
+++ b/utils/build_externals.py
@@ -12,11 +12,15 @@ Usage? Just run it.
utils/build_externals.py
"""
-import compileall, logging, os, shutil, sys, tempfile, time, urllib2
+import compileall, logging, os, sha, shutil, sys, tempfile, time, urllib2
import subprocess, re
import common
from autotest_lib.client.common_lib import logging_config, logging_manager
from autotest_lib.client.common_lib import utils
+from autotest_lib.utils import external_packages
+
+# bring in site packages as well
+utils.import_site_module(__file__, 'autotest_lib.utils.site_external_packages')
# Where package source be fetched to relative to the top of the autotest tree.
PACKAGE_DIR = 'ExternalSource'
@@ -29,11 +33,7 @@ INSTALL_ALL = False
# Want to add more packages to fetch, build and install? See the class
-# definitions at the end of this file for examples of how to do it.
-
-
-_READ_SIZE = 64*1024
-_MAX_PACKAGE_SIZE = 100*1024*1024
+# definitions at the end of external_packages.py for examples of how to do it.
class BuildExternalsLoggingConfig(logging_config.LoggingConfig):
@@ -42,12 +42,6 @@ class BuildExternalsLoggingConfig(logging_config.LoggingConfig):
use_console=True,
verbose=verbose)
-class Error(Exception):
- """Local exception to be raised by code in this file."""
-
-class FetchError(Error):
- """Failed to fetch a package from any of its listed URLs."""
-
def main():
"""
@@ -58,7 +52,7 @@ def main():
verbose=True)
os.umask(022)
- top_of_tree = _find_top_of_autotest_tree()
+ top_of_tree = external_packages.find_top_of_autotest_tree()
package_dir = os.path.join(top_of_tree, PACKAGE_DIR)
install_dir = os.path.join(top_of_tree, INSTALL_DIR)
@@ -86,7 +80,7 @@ def main():
compileall.compile_dir(install_dir, quiet=True)
# Some things install with whacky permissions, fix that.
- system("chmod -R a+rX '%s'" % install_dir)
+ external_packages.system("chmod -R a+rX '%s'" % install_dir)
errors = fetch_errors + install_errors
for error_msg in errors:
@@ -110,7 +104,7 @@ def fetch_necessary_packages(dest_dir, install_dir):
names_to_check = sys.argv[1:]
errors = []
fetched_packages = []
- for package_class in ExternalPackage.subclasses:
+ for package_class in external_packages.ExternalPackage.subclasses:
package = package_class()
if names_to_check and package.name.lower() not in names_to_check:
continue
@@ -149,661 +143,5 @@ def build_and_install_packages(packages, install_dir):
return errors
-def _checksum_file(full_path):
- """@returns The hex checksum of a file given its pathname."""
- inputfile = open(full_path, 'rb')
- try:
- hex_sum = utils.hash('sha1', inputfile.read()).hexdigest()
- finally:
- inputfile.close()
- return hex_sum
-
-
-def _find_top_of_autotest_tree():
- """@returns The full path to the top of the autotest directory tree."""
- dirname = os.path.dirname(__file__)
- autotest_dir = os.path.abspath(os.path.join(dirname, '..'))
- return autotest_dir
-
-
-def system(commandline):
- """Same as os.system(commandline) but logs the command first."""
- logging.info(commandline)
- return os.system(commandline)
-
-
-class ExternalPackage(object):
- """
- Defines an external package with URLs to fetch its sources from and
- a build_and_install() method to unpack it, build it and install it
- beneath our own autotest/site-packages directory.
-
- Base Class. Subclass this to define packages.
-
- Attributes:
- @attribute urls - A tuple of URLs to try fetching the package from.
- @attribute local_filename - A local filename to use when saving the
- fetched package.
- @attribute hex_sum - The hex digest (currently SHA1) of this package
- to be used to verify its contents.
- @attribute module_name - The installed python module name to be used for
- for a version check. Defaults to the lower case class name with
- the word Package stripped off.
- @attribute version - The desired minimum package version.
- @attribute os_requirements - A dictionary mapping a file pathname on the
- the OS distribution to a likely name of a package the user
- needs to install on their system in order to get this file.
- @attribute name - Read only, the printable name of the package.
- @attribute subclasses - This class attribute holds a list of all defined
- subclasses. It is constructed dynamically using the metaclass.
- """
- subclasses = []
- urls = ()
- local_filename = None
- hex_sum = None
- module_name = None
- version = None
- os_requirements = None
-
-
- class __metaclass__(type):
- """Any time a subclass is defined, add it to our list."""
- def __init__(mcs, name, bases, dict):
- if name != 'ExternalPackage':
- mcs.subclasses.append(mcs)
-
-
- def __init__(self):
- self.verified_package = ''
- if not self.module_name:
- self.module_name = self.name.lower()
- self.installed_version = ''
-
-
- @property
- def name(self):
- """Return the class name with any trailing 'Package' stripped off."""
- class_name = self.__class__.__name__
- if class_name.endswith('Package'):
- return class_name[:-len('Package')]
- return class_name
-
-
- def is_needed(self, unused_install_dir):
- """@returns True if self.module_name needs to be built and installed."""
- if not self.module_name or not self.version:
- logging.warning('version and module_name required for '
- 'is_needed() check to work.')
- return True
- try:
- module = __import__(self.module_name)
- except ImportError, e:
- logging.info('Could not import %s.', self.module_name)
- return True
- self.installed_version = self._get_installed_version_from_module(module)
- logging.info('imported %s version %s.', self.module_name,
- self.installed_version)
- return self.version > self.installed_version
-
-
- def _get_installed_version_from_module(self, module):
- """Ask our module its version string and return it or '' if unknown."""
- try:
- return module.__version__
- except AttributeError:
- logging.error('could not get version from %s', module)
- return ''
-
-
- def _build_and_install(self, install_dir):
- """Subclasses MUST provide their own implementation."""
- raise NotImplementedError
-
-
- def _build_and_install_current_dir(self, install_dir):
- """
- Subclasses that use _build_and_install_from_package() MUST provide
- their own implementation of this method.
- """
- raise NotImplementedError
-
-
- def build_and_install(self, install_dir):
- """
- Builds and installs the package. It must have been fetched already.
-
- @param install_dir - The package installation directory. If it does
- not exist it will be created.
- """
- if not self.verified_package:
- raise Error('Must call fetch() first. - %s' % self.name)
- self._check_os_requirements()
- return self._build_and_install(install_dir)
-
-
- def _check_os_requirements(self):
- if not self.os_requirements:
- return
- failed = False
- for file_name, package_name in self.os_requirements.iteritems():
- if not os.path.exists(file_name):
- failed = True
- logging.error('File %s not found, %s needs it.',
- file_name, self.name)
- logging.error('Perhaps you need to install something similar '
- 'to the %s package for OS first.', package_name)
- if failed:
- raise Error('Missing OS requirements for %s. (see above)' %
- self.name)
-
-
- def _build_and_install_current_dir_setup_py(self, install_dir):
- """For use as a _build_and_install_current_dir implementation."""
- egg_path = self._build_egg_using_setup_py(setup_py='setup.py')
- if not egg_path:
- return False
- return self._install_from_egg(install_dir, egg_path)
-
-
- def _build_and_install_current_dir_setupegg_py(self, install_dir):
- """For use as a _build_and_install_current_dir implementation."""
- egg_path = self._build_egg_using_setup_py(setup_py='setupegg.py')
- if not egg_path:
- return False
- return self._install_from_egg(install_dir, egg_path)
-
-
- def _build_and_install_current_dir_noegg(self, install_dir):
- if not self._build_using_setup_py():
- return False
- return self._install_using_setup_py_and_rsync(install_dir)
-
-
- def _build_and_install_from_package(self, install_dir):
- """
- This method may be used as a _build_and_install() implementation
- for subclasses if they implement _build_and_install_current_dir().
-
- Extracts the .tar.gz file, chdirs into the extracted directory
- (which is assumed to match the tar filename) and calls
- _build_and_isntall_current_dir from there.
-
- Afterwards the build (regardless of failure) extracted .tar.gz
- directory is cleaned up.
-
- @returns True on success, False otherwise.
-
- @raises OSError If the expected extraction directory does not exist.
- """
- self._extract_compressed_package()
- if self.verified_package.endswith('.tar.gz'):
- extension = '.tar.gz'
- elif self.verified_package.endswith('.tar.bz2'):
- extension = '.tar.bz2'
- elif self.verified_package.endswith('.zip'):
- extension = '.zip'
- else:
- raise Error('Unexpected package file extension on %s' %
- self.verified_package)
- os.chdir(os.path.dirname(self.verified_package))
- os.chdir(self.local_filename[:-len(extension)])
- extracted_dir = os.getcwd()
- try:
- return self._build_and_install_current_dir(install_dir)
- finally:
- os.chdir(os.path.join(extracted_dir, '..'))
- shutil.rmtree(extracted_dir)
-
-
- def _extract_compressed_package(self):
- """Extract the fetched compressed .tar or .zip within its directory."""
- if not self.verified_package:
- raise Error('Package must have been fetched first.')
- os.chdir(os.path.dirname(self.verified_package))
- if self.verified_package.endswith('gz'):
- status = system("tar -xzf '%s'" % self.verified_package)
- elif self.verified_package.endswith('bz2'):
- status = system("tar -xjf '%s'" % self.verified_package)
- elif self.verified_package.endswith('zip'):
- status = system("unzip '%s'" % self.verified_package)
- else:
- raise Error('Unknown compression suffix on %s.' %
- self.verified_package)
- if status:
- raise Error('tar failed with %s' % (status,))
-
-
- def _build_using_setup_py(self, setup_py='setup.py'):
- """
- Assuming the cwd is the extracted python package, execute a simple
- python setup.py build.
-
- @param setup_py - The name of the setup.py file to execute.
-
- @returns True on success, False otherwise.
- """
- if not os.path.exists(setup_py):
- raise Error('%sdoes not exist in %s' % (setup_py, os.getcwd()))
- status = system("'%s' %s build" % (sys.executable, setup_py))
- if status:
- logging.error('%s build failed.' % self.name)
- return False
- return True
-
-
- def _build_egg_using_setup_py(self, setup_py='setup.py'):
- """
- Assuming the cwd is the extracted python package, execute a simple
- python setup.py bdist_egg.
-
- @param setup_py - The name of the setup.py file to execute.
-
- @returns The relative path to the resulting egg file or '' on failure.
- """
- if not os.path.exists(setup_py):
- raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
- egg_subdir = 'dist'
- if os.path.isdir(egg_subdir):
- shutil.rmtree(egg_subdir)
- status = system("'%s' %s bdist_egg" % (sys.executable, setup_py))
- if status:
- logging.error('bdist_egg of setuptools failed.')
- return ''
- # I've never seen a bdist_egg lay multiple .egg files.
- for filename in os.listdir(egg_subdir):
- if filename.endswith('.egg'):
- return os.path.join(egg_subdir, filename)
-
-
- def _install_from_egg(self, install_dir, egg_path):
- """
- Install a module from an egg file by unzipping the necessary parts
- into install_dir.
-
- @param install_dir - The installation directory.
- @param egg_path - The pathname of the egg file.
- """
- status = system("unzip -q -o -d '%s' '%s'" % (install_dir, egg_path))
- if status:
- logging.error('unzip of %s failed', egg_path)
- return False
- egg_info = os.path.join(install_dir, 'EGG-INFO')
- if os.path.isdir(egg_info):
- shutil.rmtree(egg_info)
- return True
-
-
- def _install_using_setup_py_and_rsync(self, install_dir,
- setup_py='setup.py'):
- """
- Assuming the cwd is the extracted python package, execute a simple:
-
- python setup.py install --prefix=BLA
-
- BLA will be a temporary directory that everything installed will
- be picked out of and rsynced to the appropriate place under
- install_dir afterwards.
-
- Afterwards, it deconstructs the extra lib/pythonX.Y/site-packages/
- directory tree that setuptools created and moves all installed
- site-packages directly up into install_dir itself.
-
- @param install_dir the directory for the install to happen under.
- @param setup_py - The name of the setup.py file to execute.
-
- @returns True on success, False otherwise.
- """
- if not os.path.exists(setup_py):
- raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
-
- temp_dir = tempfile.mkdtemp(dir='/var/tmp')
- try:
- status = system("'%s' %s install --no-compile --prefix='%s'"
- % (sys.executable, setup_py, temp_dir))
- if status:
- logging.error('%s install failed.' % self.name)
- return False
-
- # This makes assumptions about what python setup.py install
- # does when given a prefix. Is this always correct?
- python_xy = 'python%s' % sys.version[:3]
- if os.path.isdir(os.path.join(temp_dir, 'lib')):
- # NOTE: This ignores anything outside of the lib/ dir that
- # was installed.
- temp_site_dir = os.path.join(
- temp_dir, 'lib', python_xy, 'site-packages')
- else:
- temp_site_dir = temp_dir
-
- status = system("rsync -r '%s/' '%s/'" %
- (temp_site_dir, install_dir))
- if status:
- logging.error('%s rsync to install_dir failed.' % self.name)
- return False
- return True
- finally:
- shutil.rmtree(temp_dir)
-
-
-
- def fetch(self, dest_dir):
- """
- Fetch the package from one its URLs and save it in dest_dir.
-
- If the the package already exists in dest_dir and the checksum
- matches this code will not fetch it again.
-
- Sets the 'verified_package' attribute with the destination pathname.
-
- @param dest_dir - The destination directory to save the local file.
- If it does not exist it will be created.
-
- @returns A boolean indicating if we the package is now in dest_dir.
- @raises FetchError - When something unexpected happens.
- """
- if not os.path.exists(dest_dir):
- os.makedirs(dest_dir)
- local_path = os.path.join(dest_dir, self.local_filename)
-
- # If the package exists, verify its checksum and be happy if it is good.
- if os.path.exists(local_path):
- actual_hex_sum = _checksum_file(local_path)
- if self.hex_sum == actual_hex_sum:
- logging.info('Good checksum for existing %s package.',
- self.name)
- self.verified_package = local_path
- return True
- logging.warning('Bad checksum for existing %s package. '
- 'Re-downloading', self.name)
- os.rename(local_path, local_path + '.wrong-checksum')
-
- # Download the package from one of its urls, rejecting any if the
- # checksum does not match.
- for url in self.urls:
- logging.info('Fetching %s', url)
- try:
- url_file = urllib2.urlopen(url)
- except (urllib2.URLError, EnvironmentError):
- logging.warning('Could not fetch %s package from %s.',
- self.name, url)
- continue
- data_length = int(url_file.info().get('Content-Length',
- _MAX_PACKAGE_SIZE))
- if data_length <= 0 or data_length > _MAX_PACKAGE_SIZE:
- raise FetchError('%s from %s fails Content-Length %d '
- 'sanity check.' % (self.name, url,
- data_length))
- checksum = utils.hash('sha1')
- total_read = 0
- output = open(local_path, 'wb')
- try:
- while total_read < data_length:
- data = url_file.read(_READ_SIZE)
- if not data:
- break
- output.write(data)
- checksum.update(data)
- total_read += len(data)
- finally:
- output.close()
- if self.hex_sum != checksum.hexdigest():
- logging.warning('Bad checksum for %s fetched from %s.',
- self.name, url)
- logging.warning('Got %s', checksum.hexdigest())
- logging.warning('Expected %s', self.hex_sum)
- os.unlink(local_path)
- continue
- logging.info('Good checksum.')
- self.verified_package = local_path
- return True
- else:
- return False
-
-
-# NOTE: This class definition must come -before- all other ExternalPackage
-# classes that need to use this version of setuptools so that is is inserted
-# into the ExternalPackage.subclasses list before them.
-class SetuptoolsPackage(ExternalPackage):
- # For all known setuptools releases a string compare works for the
- # version string. Hopefully they never release a 0.10. (Their own
- # version comparison code would break if they did.)
- version = '0.6c9'
- urls = ('http://pypi.python.org/packages/source/s/setuptools/'
- 'setuptools-%s.tar.gz' % (version,),)
- local_filename = 'setuptools-%s.tar.gz' % version
- hex_sum = '79086433b341f0c1df45e10d586a7d3cc25089f1'
-
- SUDO_SLEEP_DELAY = 15
-
-
- def _build_and_install(self, install_dir):
- """Install setuptools on the system."""
- logging.info('NOTE: setuptools install does not use install_dir.')
- return self._build_and_install_from_package(install_dir)
-
-
- def _build_and_install_current_dir(self, install_dir):
- egg_path = self._build_egg_using_setup_py()
- if not egg_path:
- return False
-
- print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n'
- print 'About to run sudo to install setuptools', self.version
- print 'on your system for use by', sys.executable, '\n'
- print '!! ^C within', self.SUDO_SLEEP_DELAY, 'seconds to abort.\n'
- time.sleep(self.SUDO_SLEEP_DELAY)
-
- # Copy the egg to the local filesystem /var/tmp so that root can
- # access it properly (avoid NFS squashroot issues).
- temp_dir = tempfile.mkdtemp(dir='/var/tmp')
- try:
- shutil.copy(egg_path, temp_dir)
- egg_name = os.path.split(egg_path)[1]
- temp_egg = os.path.join(temp_dir, egg_name)
- p = subprocess.Popen(['sudo', '/bin/sh', temp_egg],
- stdout=subprocess.PIPE)
- regex = re.compile('Copying (.*?) to (.*?)\n')
- match = regex.search(p.communicate()[0])
- status = p.wait()
-
- if match:
- compiled = os.path.join(match.group(2), match.group(1))
- os.system("sudo chmod a+r '%s'" % compiled)
- finally:
- shutil.rmtree(temp_dir)
-
- if status:
- logging.error('install of setuptools from egg failed.')
- return False
- return True
-
-
-class MySQLdbPackage(ExternalPackage):
- module_name = 'MySQLdb'
- version = '1.2.2'
- urls = ('http://downloads.sourceforge.net/project/mysql-python/'
- 'mysql-python/%(version)s/MySQL-python-%(version)s.tar.gz'
- % dict(version=version),)
- local_filename = 'MySQL-python-%s.tar.gz' % version
- hex_sum = '945a04773f30091ad81743f9eb0329a3ee3de383'
-
- _build_and_install_current_dir = (
- ExternalPackage._build_and_install_current_dir_setup_py)
-
-
- def _build_and_install(self, install_dir):
- if not os.path.exists('/usr/bin/mysql_config'):
- logging.error('You need to install /usr/bin/mysql_config')
- logging.error('On Ubuntu or Debian based systems use this: '
- 'sudo apt-get install libmysqlclient15-dev')
- return False
- return self._build_and_install_from_package(install_dir)
-
-
-class DjangoPackage(ExternalPackage):
- version = '1.1.1'
- local_filename = 'Django-%s.tar.gz' % version
- urls = ('http://www.djangoproject.com/download/%s/tarball/' % version,)
- hex_sum = '441c54f0e90730bf4a55432b64519169b1e6ef20'
-
- _build_and_install = ExternalPackage._build_and_install_from_package
- _build_and_install_current_dir = (
- ExternalPackage._build_and_install_current_dir_noegg)
-
-
- def _get_installed_version_from_module(self, module):
- try:
- return module.get_version().split()[0]
- except AttributeError:
- return '0.9.6'
-
-
-
-class NumpyPackage(ExternalPackage):
- version = '1.2.1'
- local_filename = 'numpy-%s.tar.gz' % version
- urls = ('http://downloads.sourceforge.net/project/numpy/NumPy/%(version)s/'
- 'numpy-%(version)s.tar.gz' % dict(version=version),)
- hex_sum = '1aa706e733aea18eaffa70d93c0105718acb66c5'
-
- _build_and_install = ExternalPackage._build_and_install_from_package
- _build_and_install_current_dir = (
- ExternalPackage._build_and_install_current_dir_setupegg_py)
-
-
-# This requires numpy so it must be declared after numpy to guarantee that it
-# is already installed.
-class MatplotlibPackage(ExternalPackage):
- version = '0.98.5.3'
- short_version = '0.98.5'
- local_filename = 'matplotlib-%s.tar.gz' % version
- urls = ('http://downloads.sourceforge.net/project/matplotlib/matplotlib/'
- 'matplotlib-%s/matplotlib-%s.tar.gz' % (short_version, version),)
- hex_sum = '2f6c894cf407192b3b60351bcc6468c0385d47b6'
- os_requirements = {'/usr/include/ft2build.h': 'libfreetype6-dev',
- '/usr/include/png.h': 'libpng12-dev'}
-
- _build_and_install = ExternalPackage._build_and_install_from_package
- _build_and_install_current_dir = (
- ExternalPackage._build_and_install_current_dir_setupegg_py)
-
-
-class AtForkPackage(ExternalPackage):
- version = '0.1.2'
- local_filename = 'atfork-%s.zip' % version
- urls = ('http://python-atfork.googlecode.com/files/' + local_filename,)
- hex_sum = '5baa64c73e966b57fa797040585c760c502dc70b'
-
- _build_and_install = ExternalPackage._build_and_install_from_package
- _build_and_install_current_dir = (
- ExternalPackage._build_and_install_current_dir_noegg)
-
-
-class ParamikoPackage(ExternalPackage):
- version = '1.7.5'
- local_filename = 'paramiko-%s.tar.gz' % version
- urls = ('http://www.lag.net/paramiko/download/' + local_filename,)
- hex_sum = '592be7a08290070b71da63a8e6f28a803399e5c5'
-
-
- _build_and_install = ExternalPackage._build_and_install_from_package
-
-
- def _check_for_pycrypto(self):
- # NOTE(gps): Linux distros have better python-crypto packages than we
- # can easily get today via a wget due to the library's age and staleness
- # yet many security and behavior bugs are fixed by patches that distros
- # already apply. PyCrypto has a new active maintainer in 2009. Once a
- # new release is made (http://pycrypto.org/) we should add an installer.
- try:
- import Crypto
- except ImportError:
- logging.error('Please run "sudo apt-get install python-crypto" '
- 'or your Linux distro\'s equivalent.')
- return False
- return True
-
-
- def _build_and_install_current_dir(self, install_dir):
- if not self._check_for_pycrypto():
- return False
- # paramiko 1.7.4 doesn't require building, it is just a module directory
- # that we can rsync into place directly.
- if not os.path.isdir('paramiko'):
- raise Error('no paramiko directory in %s.' % os.getcwd())
- status = system("rsync -r 'paramiko' '%s/'" % install_dir)
- if status:
- logging.error('%s rsync to install_dir failed.' % self.name)
- return False
- return True
-
-
-class SimplejsonPackage(ExternalPackage):
- version = '2.0.9'
- local_filename = 'simplejson-%s.tar.gz' % version
- urls = ('http://pypi.python.org/packages/source/s/simplejson/' +
- local_filename,)
- hex_sum = 'b5b26059adbe677b06c299bed30557fcb0c7df8c'
-
- _build_and_install = ExternalPackage._build_and_install_from_package
- _build_and_install_current_dir = (
- ExternalPackage._build_and_install_current_dir_setup_py)
-
-
-class Httplib2Package(ExternalPackage):
- version = '0.6.0'
- local_filename = 'httplib2-%s.tar.gz' % version
- urls = ('http://httplib2.googlecode.com/files/' + local_filename,)
- hex_sum = '995344b2704826cc0d61a266e995b328d92445a5'
-
- _build_and_install = ExternalPackage._build_and_install_from_package
- _build_and_install_current_dir = (
- ExternalPackage._build_and_install_current_dir_noegg)
-
-
-class GwtPackage(ExternalPackage):
- """Fetch and extract a local copy of GWT used to build the frontend."""
-
- version = '1.7.0'
- local_filename = 'gwt-linux-%s.tar.bz2' % version
- urls = ('http://google-web-toolkit.googlecode.com/files/' + local_filename,)
- hex_sum = 'accb39506e1fa719ba166cf54451c91dafd9d456'
- name = 'gwt'
- about_filename = 'about.txt'
- module_name = None # Not a Python module.
-
-
- def is_needed(self, install_dir):
- gwt_dir = os.path.join(install_dir, self.name)
- about_file = os.path.join(install_dir, self.name, self.about_filename)
-
- if not os.path.exists(gwt_dir) or not os.path.exists(about_file):
- logging.info('gwt not installed for autotest')
- return True
-
- f = open(about_file, 'r')
- version_line = f.readline()
- f.close()
-
- match = re.match(r'Google Web Toolkit (.*)', version_line)
- if not match:
- logging.info('did not find gwt version')
- return True
-
- logging.info('found gwt version %s', match.group(1))
- return match.group(1) != self.version
-
-
- def build_and_install(self, install_dir):
- os.chdir(install_dir)
- self._extract_compressed_package()
- extracted_dir = self.local_filename[:-len('.tar.bz2')]
- target_dir = os.path.join(install_dir, self.name)
- if os.path.exists(target_dir):
- shutil.rmtree(target_dir)
- os.rename(extracted_dir, target_dir)
- return True
-
-
if __name__ == '__main__':
sys.exit(main())
diff --git a/utils/external_packages.py b/utils/external_packages.py
new file mode 100644
index 00000000..0db307dd
--- /dev/null
+++ b/utils/external_packages.py
@@ -0,0 +1,724 @@
+#!/usr/bin/python
+#
+# Please keep this code python 2.4 compatible and stand alone.
+
+import logging, os, shutil, sys, tempfile, time, urllib2
+import subprocess, re
+from autotest_lib.client.common_lib import utils
+
+_READ_SIZE = 64*1024
+_MAX_PACKAGE_SIZE = 100*1024*1024
+
+
+class Error(Exception):
+ """Local exception to be raised by code in this file."""
+
+class FetchError(Error):
+ """Failed to fetch a package from any of its listed URLs."""
+
+
+def _checksum_file(full_path):
+ """@returns The hex checksum of a file given its pathname."""
+ inputfile = open(full_path, 'rb')
+ try:
+ hex_sum = utils.hash('sha1', inputfile.read()).hexdigest()
+ finally:
+ inputfile.close()
+ return hex_sum
+
+
+def system(commandline):
+ """Same as os.system(commandline) but logs the command first."""
+ logging.info(commandline)
+ return os.system(commandline)
+
+
+def find_top_of_autotest_tree():
+ """@returns The full path to the top of the autotest directory tree."""
+ dirname = os.path.dirname(__file__)
+ autotest_dir = os.path.abspath(os.path.join(dirname, '..'))
+ return autotest_dir
+
+
+class ExternalPackage(object):
+ """
+ Defines an external package with URLs to fetch its sources from and
+ a build_and_install() method to unpack it, build it and install it
+ beneath our own autotest/site-packages directory.
+
+ Base Class. Subclass this to define packages.
+
+ Attributes:
+ @attribute urls - A tuple of URLs to try fetching the package from.
+ @attribute local_filename - A local filename to use when saving the
+ fetched package.
+ @attribute hex_sum - The hex digest (currently SHA1) of this package
+ to be used to verify its contents.
+ @attribute module_name - The installed python module name to be used for
+ for a version check. Defaults to the lower case class name with
+ the word Package stripped off.
+ @attribute version - The desired minimum package version.
+ @attribute os_requirements - A dictionary mapping a file pathname on the
+ the OS distribution to a likely name of a package the user
+ needs to install on their system in order to get this file.
+ @attribute name - Read only, the printable name of the package.
+ @attribute subclasses - This class attribute holds a list of all defined
+ subclasses. It is constructed dynamically using the metaclass.
+ """
+ subclasses = []
+ urls = ()
+ local_filename = None
+ hex_sum = None
+ module_name = None
+ version = None
+ os_requirements = None
+
+
+ class __metaclass__(type):
+ """Any time a subclass is defined, add it to our list."""
+ def __init__(mcs, name, bases, dict):
+ if name != 'ExternalPackage':
+ mcs.subclasses.append(mcs)
+
+
+ def __init__(self):
+ self.verified_package = ''
+ if not self.module_name:
+ self.module_name = self.name.lower()
+ self.installed_version = ''
+
+
+ @property
+ def name(self):
+ """Return the class name with any trailing 'Package' stripped off."""
+ class_name = self.__class__.__name__
+ if class_name.endswith('Package'):
+ return class_name[:-len('Package')]
+ return class_name
+
+
+ def is_needed(self, unused_install_dir):
+ """@returns True if self.module_name needs to be built and installed."""
+ if not self.module_name or not self.version:
+ logging.warning('version and module_name required for '
+ 'is_needed() check to work.')
+ return True
+ try:
+ module = __import__(self.module_name)
+ except ImportError, e:
+ logging.info('Could not import %s.', self.module_name)
+ return True
+ self.installed_version = self._get_installed_version_from_module(module)
+ logging.info('imported %s version %s.', self.module_name,
+ self.installed_version)
+ return self.version > self.installed_version
+
+
+ def _get_installed_version_from_module(self, module):
+ """Ask our module its version string and return it or '' if unknown."""
+ try:
+ return module.__version__
+ except AttributeError:
+ logging.error('could not get version from %s', module)
+ return ''
+
+
+ def _build_and_install(self, install_dir):
+ """Subclasses MUST provide their own implementation."""
+ raise NotImplementedError
+
+
+ def _build_and_install_current_dir(self, install_dir):
+ """
+ Subclasses that use _build_and_install_from_package() MUST provide
+ their own implementation of this method.
+ """
+ raise NotImplementedError
+
+
+ def build_and_install(self, install_dir):
+ """
+ Builds and installs the package. It must have been fetched already.
+
+ @param install_dir - The package installation directory. If it does
+ not exist it will be created.
+ """
+ if not self.verified_package:
+ raise Error('Must call fetch() first. - %s' % self.name)
+ self._check_os_requirements()
+ return self._build_and_install(install_dir)
+
+
+ def _check_os_requirements(self):
+ if not self.os_requirements:
+ return
+ failed = False
+ for file_name, package_name in self.os_requirements.iteritems():
+ if not os.path.exists(file_name):
+ failed = True
+ logging.error('File %s not found, %s needs it.',
+ file_name, self.name)
+ logging.error('Perhaps you need to install something similar '
+ 'to the %s package for OS first.', package_name)
+ if failed:
+ raise Error('Missing OS requirements for %s. (see above)' %
+ self.name)
+
+
+ def _build_and_install_current_dir_setup_py(self, install_dir):
+ """For use as a _build_and_install_current_dir implementation."""
+ egg_path = self._build_egg_using_setup_py(setup_py='setup.py')
+ if not egg_path:
+ return False
+ return self._install_from_egg(install_dir, egg_path)
+
+
+ def _build_and_install_current_dir_setupegg_py(self, install_dir):
+ """For use as a _build_and_install_current_dir implementation."""
+ egg_path = self._build_egg_using_setup_py(setup_py='setupegg.py')
+ if not egg_path:
+ return False
+ return self._install_from_egg(install_dir, egg_path)
+
+
+ def _build_and_install_current_dir_noegg(self, install_dir):
+ if not self._build_using_setup_py():
+ return False
+ return self._install_using_setup_py_and_rsync(install_dir)
+
+
+ def _build_and_install_from_package(self, install_dir):
+ """
+ This method may be used as a _build_and_install() implementation
+ for subclasses if they implement _build_and_install_current_dir().
+
+ Extracts the .tar.gz file, chdirs into the extracted directory
+ (which is assumed to match the tar filename) and calls
+ _build_and_isntall_current_dir from there.
+
+ Afterwards the build (regardless of failure) extracted .tar.gz
+ directory is cleaned up.
+
+ @returns True on success, False otherwise.
+
+ @raises OSError If the expected extraction directory does not exist.
+ """
+ self._extract_compressed_package()
+ if self.verified_package.endswith('.tar.gz'):
+ extension = '.tar.gz'
+ elif self.verified_package.endswith('.tar.bz2'):
+ extension = '.tar.bz2'
+ elif self.verified_package.endswith('.zip'):
+ extension = '.zip'
+ else:
+ raise Error('Unexpected package file extension on %s' %
+ self.verified_package)
+ os.chdir(os.path.dirname(self.verified_package))
+ os.chdir(self.local_filename[:-len(extension)])
+ extracted_dir = os.getcwd()
+ try:
+ return self._build_and_install_current_dir(install_dir)
+ finally:
+ os.chdir(os.path.join(extracted_dir, '..'))
+ shutil.rmtree(extracted_dir)
+
+
+ def _extract_compressed_package(self):
+ """Extract the fetched compressed .tar or .zip within its directory."""
+ if not self.verified_package:
+ raise Error('Package must have been fetched first.')
+ os.chdir(os.path.dirname(self.verified_package))
+ if self.verified_package.endswith('gz'):
+ status = system("tar -xzf '%s'" % self.verified_package)
+ elif self.verified_package.endswith('bz2'):
+ status = system("tar -xjf '%s'" % self.verified_package)
+ elif self.verified_package.endswith('zip'):
+ status = system("unzip '%s'" % self.verified_package)
+ else:
+ raise Error('Unknown compression suffix on %s.' %
+ self.verified_package)
+ if status:
+ raise Error('tar failed with %s' % (status,))
+
+
+ def _build_using_setup_py(self, setup_py='setup.py'):
+ """
+ Assuming the cwd is the extracted python package, execute a simple
+ python setup.py build.
+
+ @param setup_py - The name of the setup.py file to execute.
+
+ @returns True on success, False otherwise.
+ """
+ if not os.path.exists(setup_py):
+ raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
+ status = system("'%s' %s build" % (sys.executable, setup_py))
+ if status:
+ logging.error('%s build failed.' % self.name)
+ return False
+ return True
+
+
+ def _build_egg_using_setup_py(self, setup_py='setup.py'):
+ """
+ Assuming the cwd is the extracted python package, execute a simple
+ python setup.py bdist_egg.
+
+ @param setup_py - The name of the setup.py file to execute.
+
+ @returns The relative path to the resulting egg file or '' on failure.
+ """
+ if not os.path.exists(setup_py):
+ raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
+ egg_subdir = 'dist'
+ if os.path.isdir(egg_subdir):
+ shutil.rmtree(egg_subdir)
+ status = system("'%s' %s bdist_egg" % (sys.executable, setup_py))
+ if status:
+ logging.error('bdist_egg of setuptools failed.')
+ return ''
+ # I've never seen a bdist_egg lay multiple .egg files.
+ for filename in os.listdir(egg_subdir):
+ if filename.endswith('.egg'):
+ return os.path.join(egg_subdir, filename)
+
+
+ def _install_from_egg(self, install_dir, egg_path):
+ """
+ Install a module from an egg file by unzipping the necessary parts
+ into install_dir.
+
+ @param install_dir - The installation directory.
+ @param egg_path - The pathname of the egg file.
+ """
+ status = system("unzip -q -o -d '%s' '%s'" % (install_dir, egg_path))
+ if status:
+ logging.error('unzip of %s failed', egg_path)
+ return False
+ egg_info = os.path.join(install_dir, 'EGG-INFO')
+ if os.path.isdir(egg_info):
+ shutil.rmtree(egg_info)
+ return True
+
+
+ def _get_temp_dir(self):
+ return tempfile.mkdtemp(dir='/var/tmp')
+
+
+ def _site_packages_path(self, temp_dir):
+ # This makes assumptions about what python setup.py install
+ # does when given a prefix. Is this always correct?
+ python_xy = 'python%s' % sys.version[:3]
+ return os.path.join(temp_dir, 'lib', python_xy, 'site-packages')
+
+
+ def _install_using_setup_py_and_rsync(self, install_dir,
+ setup_py='setup.py',
+ temp_dir=None):
+ """
+ Assuming the cwd is the extracted python package, execute a simple:
+
+ python setup.py install --prefix=BLA
+
+ BLA will be a temporary directory that everything installed will
+ be picked out of and rsynced to the appropriate place under
+ install_dir afterwards.
+
+ Afterwards, it deconstructs the extra lib/pythonX.Y/site-packages/
+ directory tree that setuptools created and moves all installed
+ site-packages directly up into install_dir itself.
+
+ @param install_dir the directory for the install to happen under.
+ @param setup_py - The name of the setup.py file to execute.
+
+ @returns True on success, False otherwise.
+ """
+ if not os.path.exists(setup_py):
+ raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
+
+ if temp_dir is None:
+ temp_dir = self._get_temp_dir()
+
+ try:
+ status = system("'%s' %s install --no-compile --prefix='%s'"
+ % (sys.executable, setup_py, temp_dir))
+ if status:
+ logging.error('%s install failed.' % self.name)
+ return False
+
+ if os.path.isdir(os.path.join(temp_dir, 'lib')):
+ # NOTE: This ignores anything outside of the lib/ dir that
+ # was installed.
+ temp_site_dir = self._site_packages_path(temp_dir)
+ else:
+ temp_site_dir = temp_dir
+
+ status = system("rsync -r '%s/' '%s/'" %
+ (temp_site_dir, install_dir))
+ if status:
+ logging.error('%s rsync to install_dir failed.' % self.name)
+ return False
+ return True
+ finally:
+ shutil.rmtree(temp_dir)
+
+
+
+ def _build_using_make(self, install_dir):
+ """Build the current package using configure/make.
+
+ @returns True on success, False otherwise.
+ """
+ install_prefix = os.path.join(install_dir, 'usr', 'local')
+ status = system('./configure --prefix=%s' % install_prefix)
+ if status:
+ logging.error('./configure failed for %s', self.name)
+ return False
+ status = system('make')
+ if status:
+ logging.error('make failed for %s', self.name)
+ return False
+ status = system('make check')
+ if status:
+ logging.error('make check failed for %s', self.name)
+ return False
+ return True
+
+
+ def _install_using_make(self):
+ """Install the current package using make install.
+
+ Assumes the install path was set up while running ./configure (in
+ _build_using_make()).
+
+ @returns True on success, False otherwise.
+ """
+ status = system('make install')
+ return status == 0
+
+
+ def fetch(self, dest_dir):
+ """
+ Fetch the package from one its URLs and save it in dest_dir.
+
+ If the the package already exists in dest_dir and the checksum
+ matches this code will not fetch it again.
+
+ Sets the 'verified_package' attribute with the destination pathname.
+
+ @param dest_dir - The destination directory to save the local file.
+ If it does not exist it will be created.
+
+ @returns A boolean indicating if we the package is now in dest_dir.
+ @raises FetchError - When something unexpected happens.
+ """
+ if not os.path.exists(dest_dir):
+ os.makedirs(dest_dir)
+ local_path = os.path.join(dest_dir, self.local_filename)
+
+ # If the package exists, verify its checksum and be happy if it is good.
+ if os.path.exists(local_path):
+ actual_hex_sum = _checksum_file(local_path)
+ if self.hex_sum == actual_hex_sum:
+ logging.info('Good checksum for existing %s package.',
+ self.name)
+ self.verified_package = local_path
+ return True
+ logging.warning('Bad checksum for existing %s package. '
+ 'Re-downloading', self.name)
+ os.rename(local_path, local_path + '.wrong-checksum')
+
+ # Download the package from one of its urls, rejecting any if the
+ # checksum does not match.
+ for url in self.urls:
+ logging.info('Fetching %s', url)
+ try:
+ url_file = urllib2.urlopen(url)
+ except (urllib2.URLError, EnvironmentError):
+ logging.warning('Could not fetch %s package from %s.',
+ self.name, url)
+ continue
+ data_length = int(url_file.info().get('Content-Length',
+ _MAX_PACKAGE_SIZE))
+ if data_length <= 0 or data_length > _MAX_PACKAGE_SIZE:
+ raise FetchError('%s from %s fails Content-Length %d '
+ 'sanity check.' % (self.name, url,
+ data_length))
+ checksum = utils.hash('sha1')
+ total_read = 0
+ output = open(local_path, 'wb')
+ try:
+ while total_read < data_length:
+ data = url_file.read(_READ_SIZE)
+ if not data:
+ break
+ output.write(data)
+ checksum.update(data)
+ total_read += len(data)
+ finally:
+ output.close()
+ if self.hex_sum != checksum.hexdigest():
+ logging.warning('Bad checksum for %s fetched from %s.',
+ self.name, url)
+ logging.warning('Got %s', checksum.hexdigest())
+ logging.warning('Expected %s', self.hex_sum)
+ os.unlink(local_path)
+ continue
+ logging.info('Good checksum.')
+ self.verified_package = local_path
+ return True
+ else:
+ return False
+
+
+# NOTE: This class definition must come -before- all other ExternalPackage
+# classes that need to use this version of setuptools so that is is inserted
+# into the ExternalPackage.subclasses list before them.
+class SetuptoolsPackage(ExternalPackage):
+ # For all known setuptools releases a string compare works for the
+ # version string. Hopefully they never release a 0.10. (Their own
+ # version comparison code would break if they did.)
+ version = '0.6c9'
+ urls = ('http://pypi.python.org/packages/source/s/setuptools/'
+ 'setuptools-%s.tar.gz' % (version,),)
+ local_filename = 'setuptools-%s.tar.gz' % version
+ hex_sum = '79086433b341f0c1df45e10d586a7d3cc25089f1'
+
+ SUDO_SLEEP_DELAY = 15
+
+
+ def _build_and_install(self, install_dir):
+ """Install setuptools on the system."""
+ logging.info('NOTE: setuptools install does not use install_dir.')
+ return self._build_and_install_from_package(install_dir)
+
+
+ def _build_and_install_current_dir(self, install_dir):
+ egg_path = self._build_egg_using_setup_py()
+ if not egg_path:
+ return False
+
+ print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n'
+ print 'About to run sudo to install setuptools', self.version
+ print 'on your system for use by', sys.executable, '\n'
+ print '!! ^C within', self.SUDO_SLEEP_DELAY, 'seconds to abort.\n'
+ time.sleep(self.SUDO_SLEEP_DELAY)
+
+ # Copy the egg to the local filesystem /var/tmp so that root can
+ # access it properly (avoid NFS squashroot issues).
+ temp_dir = self._get_temp_dir()
+ try:
+ shutil.copy(egg_path, temp_dir)
+ egg_name = os.path.split(egg_path)[1]
+ temp_egg = os.path.join(temp_dir, egg_name)
+ p = subprocess.Popen(['sudo', '/bin/sh', temp_egg],
+ stdout=subprocess.PIPE)
+ regex = re.compile('Copying (.*?) to (.*?)\n')
+ match = regex.search(p.communicate()[0])
+ status = p.wait()
+
+ if match:
+ compiled = os.path.join(match.group(2), match.group(1))
+ os.system("sudo chmod a+r '%s'" % compiled)
+ finally:
+ shutil.rmtree(temp_dir)
+
+ if status:
+ logging.error('install of setuptools from egg failed.')
+ return False
+ return True
+
+
+class MySQLdbPackage(ExternalPackage):
+ module_name = 'MySQLdb'
+ version = '1.2.2'
+ urls = ('http://downloads.sourceforge.net/project/mysql-python/'
+ 'mysql-python/%(version)s/MySQL-python-%(version)s.tar.gz'
+ % dict(version=version),)
+ local_filename = 'MySQL-python-%s.tar.gz' % version
+ hex_sum = '945a04773f30091ad81743f9eb0329a3ee3de383'
+
+ _build_and_install_current_dir = (
+ ExternalPackage._build_and_install_current_dir_setup_py)
+
+
+ def _build_and_install(self, install_dir):
+ if not os.path.exists('/usr/bin/mysql_config'):
+ logging.error('You need to install /usr/bin/mysql_config')
+ logging.error('On Ubuntu or Debian based systems use this: '
+ 'sudo apt-get install libmysqlclient15-dev')
+ return False
+ return self._build_and_install_from_package(install_dir)
+
+
+class DjangoPackage(ExternalPackage):
+ version = '1.1.1'
+ local_filename = 'Django-%s.tar.gz' % version
+ urls = ('http://www.djangoproject.com/download/%s/tarball/' % version,)
+ hex_sum = '441c54f0e90730bf4a55432b64519169b1e6ef20'
+
+ _build_and_install = ExternalPackage._build_and_install_from_package
+ _build_and_install_current_dir = (
+ ExternalPackage._build_and_install_current_dir_noegg)
+
+
+ def _get_installed_version_from_module(self, module):
+ try:
+ return module.get_version().split()[0]
+ except AttributeError:
+ return '0.9.6'
+
+
+
+class NumpyPackage(ExternalPackage):
+ version = '1.2.1'
+ local_filename = 'numpy-%s.tar.gz' % version
+ urls = ('http://downloads.sourceforge.net/project/numpy/NumPy/%(version)s/'
+ 'numpy-%(version)s.tar.gz' % dict(version=version),)
+ hex_sum = '1aa706e733aea18eaffa70d93c0105718acb66c5'
+
+ _build_and_install = ExternalPackage._build_and_install_from_package
+ _build_and_install_current_dir = (
+ ExternalPackage._build_and_install_current_dir_setupegg_py)
+
+
+# This requires numpy so it must be declared after numpy to guarantee that it
+# is already installed.
+class MatplotlibPackage(ExternalPackage):
+ version = '0.98.5.3'
+ short_version = '0.98.5'
+ local_filename = 'matplotlib-%s.tar.gz' % version
+ urls = ('http://downloads.sourceforge.net/project/matplotlib/matplotlib/'
+ 'matplotlib-%s/matplotlib-%s.tar.gz' % (short_version, version),)
+ hex_sum = '2f6c894cf407192b3b60351bcc6468c0385d47b6'
+ os_requirements = {'/usr/include/ft2build.h': 'libfreetype6-dev',
+ '/usr/include/png.h': 'libpng12-dev'}
+
+ _build_and_install = ExternalPackage._build_and_install_from_package
+ _build_and_install_current_dir = (
+ ExternalPackage._build_and_install_current_dir_setupegg_py)
+
+
+class AtForkPackage(ExternalPackage):
+ version = '0.1.2'
+ local_filename = 'atfork-%s.zip' % version
+ urls = ('http://python-atfork.googlecode.com/files/' + local_filename,)
+ hex_sum = '5baa64c73e966b57fa797040585c760c502dc70b'
+
+ _build_and_install = ExternalPackage._build_and_install_from_package
+ _build_and_install_current_dir = (
+ ExternalPackage._build_and_install_current_dir_noegg)
+
+
+class ParamikoPackage(ExternalPackage):
+ version = '1.7.5'
+ local_filename = 'paramiko-%s.tar.gz' % version
+ urls = ('http://www.lag.net/paramiko/download/' + local_filename,)
+ hex_sum = '592be7a08290070b71da63a8e6f28a803399e5c5'
+
+
+ _build_and_install = ExternalPackage._build_and_install_from_package
+
+
+ def _check_for_pycrypto(self):
+ # NOTE(gps): Linux distros have better python-crypto packages than we
+ # can easily get today via a wget due to the library's age and staleness
+ # yet many security and behavior bugs are fixed by patches that distros
+ # already apply. PyCrypto has a new active maintainer in 2009. Once a
+ # new release is made (http://pycrypto.org/) we should add an installer.
+ try:
+ import Crypto
+ except ImportError:
+ logging.error('Please run "sudo apt-get install python-crypto" '
+ 'or your Linux distro\'s equivalent.')
+ return False
+ return True
+
+
+ def _build_and_install_current_dir(self, install_dir):
+ if not self._check_for_pycrypto():
+ return False
+ # paramiko 1.7.4 doesn't require building, it is just a module directory
+ # that we can rsync into place directly.
+ if not os.path.isdir('paramiko'):
+ raise Error('no paramiko directory in %s.' % os.getcwd())
+ status = system("rsync -r 'paramiko' '%s/'" % install_dir)
+ if status:
+ logging.error('%s rsync to install_dir failed.' % self.name)
+ return False
+ return True
+
+
+class SimplejsonPackage(ExternalPackage):
+ version = '2.0.9'
+ local_filename = 'simplejson-%s.tar.gz' % version
+ urls = ('http://pypi.python.org/packages/source/s/simplejson/' +
+ local_filename,)
+ hex_sum = 'b5b26059adbe677b06c299bed30557fcb0c7df8c'
+
+ _build_and_install = ExternalPackage._build_and_install_from_package
+ _build_and_install_current_dir = (
+ ExternalPackage._build_and_install_current_dir_setup_py)
+
+
+class Httplib2Package(ExternalPackage):
+ version = '0.6.0'
+ local_filename = 'httplib2-%s.tar.gz' % version
+ urls = ('http://httplib2.googlecode.com/files/' + local_filename,)
+ hex_sum = '995344b2704826cc0d61a266e995b328d92445a5'
+
+ def _get_installed_version_from_module(self, module):
+ # httplib2 doesn't contain a proper version
+ return self.version
+
+ _build_and_install = ExternalPackage._build_and_install_from_package
+ _build_and_install_current_dir = (
+ ExternalPackage._build_and_install_current_dir_noegg)
+
+
+class GwtPackage(ExternalPackage):
+ """Fetch and extract a local copy of GWT used to build the frontend."""
+
+ version = '1.7.0'
+ local_filename = 'gwt-linux-%s.tar.bz2' % version
+ urls = ('http://google-web-toolkit.googlecode.com/files/' + local_filename,)
+ hex_sum = 'accb39506e1fa719ba166cf54451c91dafd9d456'
+ name = 'gwt'
+ about_filename = 'about.txt'
+ module_name = None # Not a Python module.
+
+
+ def is_needed(self, install_dir):
+ gwt_dir = os.path.join(install_dir, self.name)
+ about_file = os.path.join(install_dir, self.name, self.about_filename)
+
+ if not os.path.exists(gwt_dir) or not os.path.exists(about_file):
+ logging.info('gwt not installed for autotest')
+ return True
+
+ f = open(about_file, 'r')
+ version_line = f.readline()
+ f.close()
+
+ match = re.match(r'Google Web Toolkit (.*)', version_line)
+ if not match:
+ logging.info('did not find gwt version')
+ return True
+
+ logging.info('found gwt version %s', match.group(1))
+ return match.group(1) != self.version
+
+
+ def build_and_install(self, install_dir):
+ os.chdir(install_dir)
+ self._extract_compressed_package()
+ extracted_dir = self.local_filename[:-len('.tar.bz2')]
+ target_dir = os.path.join(install_dir, self.name)
+ if os.path.exists(target_dir):
+ shutil.rmtree(target_dir)
+ os.rename(extracted_dir, target_dir)
+ return True
+
+
+if __name__ == '__main__':
+ sys.exit(main())