Add paramiko and GWT to build_externals.

Signed-off-by: Gregory Smith <gps@google.com>


git-svn-id: http://test.kernel.org/svn/autotest/trunk@3362 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/utils/build_externals.py b/utils/build_externals.py
index e661eb5..7937219 100644
--- a/utils/build_externals.py
+++ b/utils/build_externals.py
@@ -45,7 +45,8 @@
     Find all ExternalPackage classes defined in this file and ask them to
     fetch, build and install themselves.
     """
-    logging.basicConfig(level=logging.INFO)
+    _configure_logging()
+    os.umask(022)
 
     top_of_tree = _find_top_of_autotest_tree()
     package_dir = os.path.join(top_of_tree, PACKAGE_DIR)
@@ -59,7 +60,8 @@
         os.environ['PYTHONPATH'] = ':'.join([
             install_dir, os.environ.get('PYTHONPATH', '')])
 
-    fetched_packages, fetch_errors = fetch_necessary_packages(package_dir)
+    fetched_packages, fetch_errors = fetch_necessary_packages(package_dir,
+                                                              install_dir)
     install_errors = build_and_install_packages(fetched_packages, install_dir)
 
     # Byte compile the code after it has been installed in its final
@@ -80,11 +82,25 @@
     return len(errors)
 
 
-def fetch_necessary_packages(dest_dir):
+def _configure_logging(level=logging.INFO):
+    """Setup familiar looking console log message output."""
+    root_logger = logging.getLogger()
+    root_logger.setLevel(level)
+    console_handler = logging.StreamHandler()
+    console_handler.setLevel(level)
+    console_formatter = logging.Formatter(
+            fmt='[%(asctime)s %(levelname)-5.5s] %(message)s',
+            datefmt='%H:%M:%S')
+    console_handler.setFormatter(console_formatter)
+    root_logger.addHandler(console_handler)
+
+
+def fetch_necessary_packages(dest_dir, install_dir):
     """
     Fetches all ExternalPackages into dest_dir.
 
-    @param dest_dir - Directory the packages should be fetched into.
+    @param dest_dir: Directory the packages should be fetched into.
+    @param install_dir: Directory where packages will later installed.
 
     @returns A tuple containing two lists:
              * A list of ExternalPackage instances that were fetched and
@@ -98,7 +114,7 @@
         package = package_class()
         if names_to_check and package.name.lower() not in names_to_check:
             continue
-        if not package.is_needed():
+        if not package.is_needed(install_dir):
             logging.info('A new %s is not needed on this system.',
                          package.name)
             if INSTALL_ALL:
@@ -209,7 +225,7 @@
         return class_name
 
 
-    def is_needed(self):
+    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 '
@@ -292,7 +308,7 @@
 
         @raises OSError If the expected extraction directory does not exist.
         """
-        self._extract_tar_gz()
+        self._extract_compressed_tar()
         os.chdir(os.path.dirname(self.verified_package))
         os.chdir(self.local_filename[:-len('.tar.gz')])
         extracted_dir = os.getcwd()
@@ -303,12 +319,18 @@
             shutil.rmtree(extracted_dir)
 
 
-    def _extract_tar_gz(self):
-        """Extract the fetched gzipped tar file within its directory."""
+    def _extract_compressed_tar(self):
+        """Extract the fetched compressed tar file within its directory."""
         if not self.verified_package:
             raise Error('Package must have been fetched first.')
         os.chdir(os.path.dirname(self.verified_package))
-        status = system("tar -xzf '%s'" % 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)
+        else:
+            raise Error('Unknown compression suffix on %s.' %
+                        self.verified_package)
         if status:
             raise Error('tar failed with %s' % (status,))
 
@@ -463,7 +485,7 @@
             logging.info('Fetching %s', url)
             try:
                 url_file = urllib2.urlopen(url)
-            except (urllib2.URLError, os.EnvironmentError):
+            except (urllib2.URLError, EnvironmentError):
                 logging.warning('Could not fetch %s package from %s.',
                                 self.name, url)
                 continue
@@ -588,7 +610,10 @@
 
 
     def _get_installed_version_from_module(self, module):
-        return module.get_version().split()[0]
+        try:
+            return module.get_version().split()[0]
+        except AttributeError:
+            return '0.9.6'
 
 
     def _build_and_install_current_dir(self, install_dir):
@@ -614,9 +639,9 @@
 # is already installed.
 class MatplotlibPackage(ExternalPackage):
     version = '0.98.5.2'
-    urls = ('http://dl.sourceforge.net/sourceforge/matplotlib/'
-            'matplotlib-%s.tar.gz' % (version,),)
     local_filename = 'matplotlib-%s.tar.gz' % version
+    urls = ('http://dl.sourceforge.net/sourceforge/matplotlib/' +
+            local_filename,)
     hex_sum = 'fbce043555de4f5a34e2a47e200527720a90b370'
 
     _build_and_install = ExternalPackage._build_and_install_from_tar_gz
@@ -624,5 +649,68 @@
             ExternalPackage._build_and_install_current_dir_setupegg_py)
 
 
+class ParamikoPackage(ExternalPackage):
+    version = '1.7.4'
+    local_filename = 'paramiko-%s.tar.gz' % version
+    urls = ('http://www.lag.net/paramiko/download/' + local_filename,)
+    hex_sum = 'a33e9c3fbd63f7e3a83278179a4d436e5b28347f'
+
+
+    _build_and_install = ExternalPackage._build_and_install_from_tar_gz
+
+
+    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 GwtPackage(ExternalPackage):
+    """Fetch and extract a local copy of GWT used to build the frontend."""
+
+    version = '1.6.4'
+    local_filename = 'gwt-linux-%s.tar.bz2' % version
+    urls = ('http://google-web-toolkit.googlecode.com/files/' + local_filename,)
+    hex_sum = '480882f630993da727eaae464341a5ff86f9b7d7'
+    name = 'gwt'
+    module_name = None  # Not a Python module.
+
+
+    def is_needed(self, install_dir):
+        return not os.path.exists(os.path.join(install_dir, self.name))
+
+
+    def build_and_install(self, install_dir):
+        os.chdir(install_dir)
+        self._extract_compressed_tar()
+        extracted_dir = self.local_filename[:-len('.tar.bz2')]
+        target_dir = os.path.join(install_dir, self.name)
+        os.rename(extracted_dir, target_dir)
+        return True
+
+
 if __name__ == '__main__':
     sys.exit(main())