external_packages.py revision d6a4718a15d37306f31a65ad8d6b9023b4a69b42
1# Please keep this code python 2.4 compatible and stand alone. 2 3import logging, os, shutil, sys, tempfile, time, urllib2 4import subprocess, re 5from autotest_lib.client.common_lib import utils 6 7_READ_SIZE = 64*1024 8_MAX_PACKAGE_SIZE = 100*1024*1024 9 10 11class Error(Exception): 12 """Local exception to be raised by code in this file.""" 13 14class FetchError(Error): 15 """Failed to fetch a package from any of its listed URLs.""" 16 17 18def _checksum_file(full_path): 19 """@returns The hex checksum of a file given its pathname.""" 20 inputfile = open(full_path, 'rb') 21 try: 22 hex_sum = utils.hash('sha1', inputfile.read()).hexdigest() 23 finally: 24 inputfile.close() 25 return hex_sum 26 27 28def system(commandline): 29 """Same as os.system(commandline) but logs the command first.""" 30 logging.info(commandline) 31 return os.system(commandline) 32 33 34def find_top_of_autotest_tree(): 35 """@returns The full path to the top of the autotest directory tree.""" 36 dirname = os.path.dirname(__file__) 37 autotest_dir = os.path.abspath(os.path.join(dirname, '..')) 38 return autotest_dir 39 40 41class ExternalPackage(object): 42 """ 43 Defines an external package with URLs to fetch its sources from and 44 a build_and_install() method to unpack it, build it and install it 45 beneath our own autotest/site-packages directory. 46 47 Base Class. Subclass this to define packages. 48 49 Attributes: 50 @attribute urls - A tuple of URLs to try fetching the package from. 51 @attribute local_filename - A local filename to use when saving the 52 fetched package. 53 @attribute hex_sum - The hex digest (currently SHA1) of this package 54 to be used to verify its contents. 55 @attribute module_name - The installed python module name to be used for 56 for a version check. Defaults to the lower case class name with 57 the word Package stripped off. 58 @attribute version - The desired minimum package version. 59 @attribute os_requirements - A dictionary mapping a file pathname on the 60 the OS distribution to a likely name of a package the user 61 needs to install on their system in order to get this file. 62 @attribute name - Read only, the printable name of the package. 63 @attribute subclasses - This class attribute holds a list of all defined 64 subclasses. It is constructed dynamically using the metaclass. 65 """ 66 subclasses = [] 67 urls = () 68 local_filename = None 69 hex_sum = None 70 module_name = None 71 version = None 72 os_requirements = None 73 74 75 class __metaclass__(type): 76 """Any time a subclass is defined, add it to our list.""" 77 def __init__(mcs, name, bases, dict): 78 if name != 'ExternalPackage': 79 mcs.subclasses.append(mcs) 80 81 82 def __init__(self): 83 self.verified_package = '' 84 if not self.module_name: 85 self.module_name = self.name.lower() 86 self.installed_version = '' 87 88 89 @property 90 def name(self): 91 """Return the class name with any trailing 'Package' stripped off.""" 92 class_name = self.__class__.__name__ 93 if class_name.endswith('Package'): 94 return class_name[:-len('Package')] 95 return class_name 96 97 98 def is_needed(self, unused_install_dir): 99 """@returns True if self.module_name needs to be built and installed.""" 100 if not self.module_name or not self.version: 101 logging.warning('version and module_name required for ' 102 'is_needed() check to work.') 103 return True 104 try: 105 module = __import__(self.module_name) 106 except ImportError, e: 107 logging.info("%s isn't present. Will install.", self.module_name) 108 return True 109 self.installed_version = self._get_installed_version_from_module(module) 110 logging.info('imported %s version %s.', self.module_name, 111 self.installed_version) 112 if hasattr(self, 'minimum_version'): 113 return self.minimum_version > self.installed_version 114 else: 115 return self.version > self.installed_version 116 117 118 def _get_installed_version_from_module(self, module): 119 """Ask our module its version string and return it or '' if unknown.""" 120 try: 121 return module.__version__ 122 except AttributeError: 123 logging.error('could not get version from %s', module) 124 return '' 125 126 127 def _build_and_install(self, install_dir): 128 """Subclasses MUST provide their own implementation.""" 129 raise NotImplementedError 130 131 132 def _build_and_install_current_dir(self, install_dir): 133 """ 134 Subclasses that use _build_and_install_from_package() MUST provide 135 their own implementation of this method. 136 """ 137 raise NotImplementedError 138 139 140 def build_and_install(self, install_dir): 141 """ 142 Builds and installs the package. It must have been fetched already. 143 144 @param install_dir - The package installation directory. If it does 145 not exist it will be created. 146 """ 147 if not self.verified_package: 148 raise Error('Must call fetch() first. - %s' % self.name) 149 self._check_os_requirements() 150 return self._build_and_install(install_dir) 151 152 153 def _check_os_requirements(self): 154 if not self.os_requirements: 155 return 156 failed = False 157 for file_name, package_name in self.os_requirements.iteritems(): 158 if not os.path.exists(file_name): 159 failed = True 160 logging.error('File %s not found, %s needs it.', 161 file_name, self.name) 162 logging.error('Perhaps you need to install something similar ' 163 'to the %s package for OS first.', package_name) 164 if failed: 165 raise Error('Missing OS requirements for %s. (see above)' % 166 self.name) 167 168 169 def _build_and_install_current_dir_setup_py(self, install_dir): 170 """For use as a _build_and_install_current_dir implementation.""" 171 egg_path = self._build_egg_using_setup_py(setup_py='setup.py') 172 if not egg_path: 173 return False 174 return self._install_from_egg(install_dir, egg_path) 175 176 177 def _build_and_install_current_dir_setupegg_py(self, install_dir): 178 """For use as a _build_and_install_current_dir implementation.""" 179 egg_path = self._build_egg_using_setup_py(setup_py='setupegg.py') 180 if not egg_path: 181 return False 182 return self._install_from_egg(install_dir, egg_path) 183 184 185 def _build_and_install_current_dir_noegg(self, install_dir): 186 if not self._build_using_setup_py(): 187 return False 188 return self._install_using_setup_py_and_rsync(install_dir) 189 190 191 def _build_and_install_from_package(self, install_dir): 192 """ 193 This method may be used as a _build_and_install() implementation 194 for subclasses if they implement _build_and_install_current_dir(). 195 196 Extracts the .tar.gz file, chdirs into the extracted directory 197 (which is assumed to match the tar filename) and calls 198 _build_and_isntall_current_dir from there. 199 200 Afterwards the build (regardless of failure) extracted .tar.gz 201 directory is cleaned up. 202 203 @returns True on success, False otherwise. 204 205 @raises OSError If the expected extraction directory does not exist. 206 """ 207 self._extract_compressed_package() 208 if self.verified_package.endswith('.tar.gz'): 209 extension = '.tar.gz' 210 elif self.verified_package.endswith('.tar.bz2'): 211 extension = '.tar.bz2' 212 elif self.verified_package.endswith('.zip'): 213 extension = '.zip' 214 else: 215 raise Error('Unexpected package file extension on %s' % 216 self.verified_package) 217 os.chdir(os.path.dirname(self.verified_package)) 218 os.chdir(self.local_filename[:-len(extension)]) 219 extracted_dir = os.getcwd() 220 try: 221 return self._build_and_install_current_dir(install_dir) 222 finally: 223 os.chdir(os.path.join(extracted_dir, '..')) 224 shutil.rmtree(extracted_dir) 225 226 227 def _extract_compressed_package(self): 228 """Extract the fetched compressed .tar or .zip within its directory.""" 229 if not self.verified_package: 230 raise Error('Package must have been fetched first.') 231 os.chdir(os.path.dirname(self.verified_package)) 232 if self.verified_package.endswith('gz'): 233 status = system("tar -xzf '%s'" % self.verified_package) 234 elif self.verified_package.endswith('bz2'): 235 status = system("tar -xjf '%s'" % self.verified_package) 236 elif self.verified_package.endswith('zip'): 237 status = system("unzip '%s'" % self.verified_package) 238 else: 239 raise Error('Unknown compression suffix on %s.' % 240 self.verified_package) 241 if status: 242 raise Error('tar failed with %s' % (status,)) 243 244 245 def _build_using_setup_py(self, setup_py='setup.py'): 246 """ 247 Assuming the cwd is the extracted python package, execute a simple 248 python setup.py build. 249 250 @param setup_py - The name of the setup.py file to execute. 251 252 @returns True on success, False otherwise. 253 """ 254 if not os.path.exists(setup_py): 255 raise Error('%s does not exist in %s' % (setup_py, os.getcwd())) 256 status = system("'%s' %s build" % (sys.executable, setup_py)) 257 if status: 258 logging.error('%s build failed.' % self.name) 259 return False 260 return True 261 262 263 def _build_egg_using_setup_py(self, setup_py='setup.py'): 264 """ 265 Assuming the cwd is the extracted python package, execute a simple 266 python setup.py bdist_egg. 267 268 @param setup_py - The name of the setup.py file to execute. 269 270 @returns The relative path to the resulting egg file or '' on failure. 271 """ 272 if not os.path.exists(setup_py): 273 raise Error('%s does not exist in %s' % (setup_py, os.getcwd())) 274 egg_subdir = 'dist' 275 if os.path.isdir(egg_subdir): 276 shutil.rmtree(egg_subdir) 277 status = system("'%s' %s bdist_egg" % (sys.executable, setup_py)) 278 if status: 279 logging.error('bdist_egg of setuptools failed.') 280 return '' 281 # I've never seen a bdist_egg lay multiple .egg files. 282 for filename in os.listdir(egg_subdir): 283 if filename.endswith('.egg'): 284 return os.path.join(egg_subdir, filename) 285 286 287 def _install_from_egg(self, install_dir, egg_path): 288 """ 289 Install a module from an egg file by unzipping the necessary parts 290 into install_dir. 291 292 @param install_dir - The installation directory. 293 @param egg_path - The pathname of the egg file. 294 """ 295 status = system("unzip -q -o -d '%s' '%s'" % (install_dir, egg_path)) 296 if status: 297 logging.error('unzip of %s failed', egg_path) 298 return False 299 egg_info = os.path.join(install_dir, 'EGG-INFO') 300 if os.path.isdir(egg_info): 301 shutil.rmtree(egg_info) 302 return True 303 304 305 def _get_temp_dir(self): 306 return tempfile.mkdtemp(dir='/var/tmp') 307 308 309 def _site_packages_path(self, temp_dir): 310 # This makes assumptions about what python setup.py install 311 # does when given a prefix. Is this always correct? 312 python_xy = 'python%s' % sys.version[:3] 313 return os.path.join(temp_dir, 'lib', python_xy, 'site-packages') 314 315 316 def _install_using_setup_py_and_rsync(self, install_dir, 317 setup_py='setup.py', 318 temp_dir=None): 319 """ 320 Assuming the cwd is the extracted python package, execute a simple: 321 322 python setup.py install --prefix=BLA 323 324 BLA will be a temporary directory that everything installed will 325 be picked out of and rsynced to the appropriate place under 326 install_dir afterwards. 327 328 Afterwards, it deconstructs the extra lib/pythonX.Y/site-packages/ 329 directory tree that setuptools created and moves all installed 330 site-packages directly up into install_dir itself. 331 332 @param install_dir the directory for the install to happen under. 333 @param setup_py - The name of the setup.py file to execute. 334 335 @returns True on success, False otherwise. 336 """ 337 if not os.path.exists(setup_py): 338 raise Error('%s does not exist in %s' % (setup_py, os.getcwd())) 339 340 if temp_dir is None: 341 temp_dir = self._get_temp_dir() 342 343 try: 344 status = system("'%s' %s install --no-compile --prefix='%s'" 345 % (sys.executable, setup_py, temp_dir)) 346 if status: 347 logging.error('%s install failed.' % self.name) 348 return False 349 350 if os.path.isdir(os.path.join(temp_dir, 'lib')): 351 # NOTE: This ignores anything outside of the lib/ dir that 352 # was installed. 353 temp_site_dir = self._site_packages_path(temp_dir) 354 else: 355 temp_site_dir = temp_dir 356 357 status = system("rsync -r '%s/' '%s/'" % 358 (temp_site_dir, install_dir)) 359 if status: 360 logging.error('%s rsync to install_dir failed.' % self.name) 361 return False 362 return True 363 finally: 364 shutil.rmtree(temp_dir) 365 366 367 368 def _build_using_make(self, install_dir): 369 """Build the current package using configure/make. 370 371 @returns True on success, False otherwise. 372 """ 373 install_prefix = os.path.join(install_dir, 'usr', 'local') 374 status = system('./configure --prefix=%s' % install_prefix) 375 if status: 376 logging.error('./configure failed for %s', self.name) 377 return False 378 status = system('make') 379 if status: 380 logging.error('make failed for %s', self.name) 381 return False 382 status = system('make check') 383 if status: 384 logging.error('make check failed for %s', self.name) 385 return False 386 return True 387 388 389 def _install_using_make(self): 390 """Install the current package using make install. 391 392 Assumes the install path was set up while running ./configure (in 393 _build_using_make()). 394 395 @returns True on success, False otherwise. 396 """ 397 status = system('make install') 398 return status == 0 399 400 401 def fetch(self, dest_dir): 402 """ 403 Fetch the package from one its URLs and save it in dest_dir. 404 405 If the the package already exists in dest_dir and the checksum 406 matches this code will not fetch it again. 407 408 Sets the 'verified_package' attribute with the destination pathname. 409 410 @param dest_dir - The destination directory to save the local file. 411 If it does not exist it will be created. 412 413 @returns A boolean indicating if we the package is now in dest_dir. 414 @raises FetchError - When something unexpected happens. 415 """ 416 if not os.path.exists(dest_dir): 417 os.makedirs(dest_dir) 418 local_path = os.path.join(dest_dir, self.local_filename) 419 420 # If the package exists, verify its checksum and be happy if it is good. 421 if os.path.exists(local_path): 422 actual_hex_sum = _checksum_file(local_path) 423 if self.hex_sum == actual_hex_sum: 424 logging.info('Good checksum for existing %s package.', 425 self.name) 426 self.verified_package = local_path 427 return True 428 logging.warning('Bad checksum for existing %s package. ' 429 'Re-downloading', self.name) 430 os.rename(local_path, local_path + '.wrong-checksum') 431 432 # Download the package from one of its urls, rejecting any if the 433 # checksum does not match. 434 for url in self.urls: 435 logging.info('Fetching %s', url) 436 try: 437 url_file = urllib2.urlopen(url) 438 except (urllib2.URLError, EnvironmentError): 439 logging.warning('Could not fetch %s package from %s.', 440 self.name, url) 441 continue 442 data_length = int(url_file.info().get('Content-Length', 443 _MAX_PACKAGE_SIZE)) 444 if data_length <= 0 or data_length > _MAX_PACKAGE_SIZE: 445 raise FetchError('%s from %s fails Content-Length %d ' 446 'sanity check.' % (self.name, url, 447 data_length)) 448 checksum = utils.hash('sha1') 449 total_read = 0 450 output = open(local_path, 'wb') 451 try: 452 while total_read < data_length: 453 data = url_file.read(_READ_SIZE) 454 if not data: 455 break 456 output.write(data) 457 checksum.update(data) 458 total_read += len(data) 459 finally: 460 output.close() 461 if self.hex_sum != checksum.hexdigest(): 462 logging.warning('Bad checksum for %s fetched from %s.', 463 self.name, url) 464 logging.warning('Got %s', checksum.hexdigest()) 465 logging.warning('Expected %s', self.hex_sum) 466 os.unlink(local_path) 467 continue 468 logging.info('Good checksum.') 469 self.verified_package = local_path 470 return True 471 else: 472 return False 473 474 475# NOTE: This class definition must come -before- all other ExternalPackage 476# classes that need to use this version of setuptools so that is is inserted 477# into the ExternalPackage.subclasses list before them. 478class SetuptoolsPackage(ExternalPackage): 479 # For all known setuptools releases a string compare works for the 480 # version string. Hopefully they never release a 0.10. (Their own 481 # version comparison code would break if they did.) 482 # Any system with setuptools > 0.6 is fine. If none installed, then 483 # try to install the latest found on the upstream. 484 minimum_version = '0.6' 485 version = '0.6c11' 486 urls = ('http://pypi.python.org/packages/source/s/setuptools/' 487 'setuptools-%s.tar.gz' % (version,),) 488 local_filename = 'setuptools-%s.tar.gz' % version 489 hex_sum = '8d1ad6384d358c547c50c60f1bfdb3362c6c4a7d' 490 491 SUDO_SLEEP_DELAY = 15 492 493 494 def _build_and_install(self, install_dir): 495 """Install setuptools on the system.""" 496 logging.info('NOTE: setuptools install does not use install_dir.') 497 return self._build_and_install_from_package(install_dir) 498 499 500 def _build_and_install_current_dir(self, install_dir): 501 egg_path = self._build_egg_using_setup_py() 502 if not egg_path: 503 return False 504 505 print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n' 506 print 'About to run sudo to install setuptools', self.version 507 print 'on your system for use by', sys.executable, '\n' 508 print '!! ^C within', self.SUDO_SLEEP_DELAY, 'seconds to abort.\n' 509 time.sleep(self.SUDO_SLEEP_DELAY) 510 511 # Copy the egg to the local filesystem /var/tmp so that root can 512 # access it properly (avoid NFS squashroot issues). 513 temp_dir = self._get_temp_dir() 514 try: 515 shutil.copy(egg_path, temp_dir) 516 egg_name = os.path.split(egg_path)[1] 517 temp_egg = os.path.join(temp_dir, egg_name) 518 p = subprocess.Popen(['sudo', '/bin/sh', temp_egg], 519 stdout=subprocess.PIPE) 520 regex = re.compile('Copying (.*?) to (.*?)\n') 521 match = regex.search(p.communicate()[0]) 522 status = p.wait() 523 524 if match: 525 compiled = os.path.join(match.group(2), match.group(1)) 526 os.system("sudo chmod a+r '%s'" % compiled) 527 finally: 528 shutil.rmtree(temp_dir) 529 530 if status: 531 logging.error('install of setuptools from egg failed.') 532 return False 533 return True 534 535 536class MySQLdbPackage(ExternalPackage): 537 module_name = 'MySQLdb' 538 version = '1.2.2' 539 urls = ('http://downloads.sourceforge.net/project/mysql-python/' 540 'mysql-python/%(version)s/MySQL-python-%(version)s.tar.gz' 541 % dict(version=version),) 542 local_filename = 'MySQL-python-%s.tar.gz' % version 543 hex_sum = '945a04773f30091ad81743f9eb0329a3ee3de383' 544 545 _build_and_install_current_dir = ( 546 ExternalPackage._build_and_install_current_dir_setup_py) 547 548 549 def _build_and_install(self, install_dir): 550 if not os.path.exists('/usr/bin/mysql_config'): 551 logging.error('You need to install /usr/bin/mysql_config') 552 logging.error('On Ubuntu or Debian based systems use this: ' 553 'sudo apt-get install libmysqlclient15-dev') 554 return False 555 return self._build_and_install_from_package(install_dir) 556 557 558class DjangoPackage(ExternalPackage): 559 version = '1.3' 560 local_filename = 'Django-%s.tar.gz' % version 561 urls = ('http://www.djangoproject.com/download/%s/tarball/' % version,) 562 hex_sum = 'f8814d5e1412bb932318db5130260da5bf053ff7' 563 564 _build_and_install = ExternalPackage._build_and_install_from_package 565 _build_and_install_current_dir = ( 566 ExternalPackage._build_and_install_current_dir_noegg) 567 568 569 def _get_installed_version_from_module(self, module): 570 try: 571 return module.get_version().split()[0] 572 except AttributeError: 573 return '0.9.6' 574 575 576 577class NumpyPackage(ExternalPackage): 578 version = '1.2.1' 579 local_filename = 'numpy-%s.tar.gz' % version 580 urls = ('http://downloads.sourceforge.net/project/numpy/NumPy/%(version)s/' 581 'numpy-%(version)s.tar.gz' % dict(version=version),) 582 hex_sum = '1aa706e733aea18eaffa70d93c0105718acb66c5' 583 584 _build_and_install = ExternalPackage._build_and_install_from_package 585 _build_and_install_current_dir = ( 586 ExternalPackage._build_and_install_current_dir_setupegg_py) 587 588 589# This requires numpy so it must be declared after numpy to guarantee that it 590# is already installed. 591class MatplotlibPackage(ExternalPackage): 592 version = '0.98.5.3' 593 short_version = '0.98.5' 594 local_filename = 'matplotlib-%s.tar.gz' % version 595 urls = ('http://downloads.sourceforge.net/project/matplotlib/matplotlib/' 596 'matplotlib-%s/matplotlib-%s.tar.gz' % (short_version, version),) 597 hex_sum = '2f6c894cf407192b3b60351bcc6468c0385d47b6' 598 os_requirements = {'/usr/include/ft2build.h': 'libfreetype6-dev', 599 '/usr/include/png.h': 'libpng12-dev'} 600 601 _build_and_install = ExternalPackage._build_and_install_from_package 602 _build_and_install_current_dir = ( 603 ExternalPackage._build_and_install_current_dir_setupegg_py) 604 605 606class AtForkPackage(ExternalPackage): 607 version = '0.1.2' 608 local_filename = 'atfork-%s.zip' % version 609 urls = ('http://python-atfork.googlecode.com/files/' + local_filename,) 610 hex_sum = '5baa64c73e966b57fa797040585c760c502dc70b' 611 612 _build_and_install = ExternalPackage._build_and_install_from_package 613 _build_and_install_current_dir = ( 614 ExternalPackage._build_and_install_current_dir_noegg) 615 616 617class ParamikoPackage(ExternalPackage): 618 version = '1.7.5' 619 local_filename = 'paramiko-%s.tar.gz' % version 620 urls = ('http://www.lag.net/paramiko/download/' + local_filename, 621 'ftp://mirrors.kernel.org/gentoo/distfiles/' + local_filename,) 622 hex_sum = '592be7a08290070b71da63a8e6f28a803399e5c5' 623 624 625 _build_and_install = ExternalPackage._build_and_install_from_package 626 627 628 def _check_for_pycrypto(self): 629 # NOTE(gps): Linux distros have better python-crypto packages than we 630 # can easily get today via a wget due to the library's age and staleness 631 # yet many security and behavior bugs are fixed by patches that distros 632 # already apply. PyCrypto has a new active maintainer in 2009. Once a 633 # new release is made (http://pycrypto.org/) we should add an installer. 634 try: 635 import Crypto 636 except ImportError: 637 logging.error('Please run "sudo apt-get install python-crypto" ' 638 'or your Linux distro\'s equivalent.') 639 return False 640 return True 641 642 643 def _build_and_install_current_dir(self, install_dir): 644 if not self._check_for_pycrypto(): 645 return False 646 # paramiko 1.7.4 doesn't require building, it is just a module directory 647 # that we can rsync into place directly. 648 if not os.path.isdir('paramiko'): 649 raise Error('no paramiko directory in %s.' % os.getcwd()) 650 status = system("rsync -r 'paramiko' '%s/'" % install_dir) 651 if status: 652 logging.error('%s rsync to install_dir failed.' % self.name) 653 return False 654 return True 655 656 657class RequestsPackage(ExternalPackage): 658 version = '0.11.2' 659 local_filename = 'requests-%s.tar.gz' % version 660 urls = ('http://pypi.python.org/packages/source/r/requests/' + 661 local_filename,) 662 hex_sum = '00a49e8bd6dd8955acf6f6269d1b85f50c70b712' 663 664 _build_and_install = ExternalPackage._build_and_install_from_package 665 _build_and_install_current_dir = ( 666 ExternalPackage._build_and_install_current_dir_setup_py) 667 668 669class SimplejsonPackage(ExternalPackage): 670 version = '2.0.9' 671 local_filename = 'simplejson-%s.tar.gz' % version 672 urls = ('http://pypi.python.org/packages/source/s/simplejson/' + 673 local_filename,) 674 hex_sum = 'b5b26059adbe677b06c299bed30557fcb0c7df8c' 675 676 _build_and_install = ExternalPackage._build_and_install_from_package 677 _build_and_install_current_dir = ( 678 ExternalPackage._build_and_install_current_dir_setup_py) 679 680 681class Httplib2Package(ExternalPackage): 682 version = '0.6.0' 683 local_filename = 'httplib2-%s.tar.gz' % version 684 urls = ('http://httplib2.googlecode.com/files/' + local_filename,) 685 hex_sum = '995344b2704826cc0d61a266e995b328d92445a5' 686 687 def _get_installed_version_from_module(self, module): 688 # httplib2 doesn't contain a proper version 689 return self.version 690 691 _build_and_install = ExternalPackage._build_and_install_from_package 692 _build_and_install_current_dir = ( 693 ExternalPackage._build_and_install_current_dir_noegg) 694 695 696class GwtPackage(ExternalPackage): 697 """Fetch and extract a local copy of GWT used to build the frontend.""" 698 699 version = '2.3.0' 700 local_filename = 'gwt-%s.zip' % version 701 urls = ('http://google-web-toolkit.googlecode.com/files/' + local_filename,) 702 hex_sum = 'd51fce9166e6b31349659ffca89baf93e39bc84b' 703 name = 'gwt' 704 about_filename = 'about.txt' 705 module_name = None # Not a Python module. 706 707 708 def is_needed(self, install_dir): 709 gwt_dir = os.path.join(install_dir, self.name) 710 about_file = os.path.join(install_dir, self.name, self.about_filename) 711 712 if not os.path.exists(gwt_dir) or not os.path.exists(about_file): 713 logging.info('gwt not installed for autotest') 714 return True 715 716 f = open(about_file, 'r') 717 version_line = f.readline() 718 f.close() 719 720 match = re.match(r'Google Web Toolkit (.*)', version_line) 721 if not match: 722 logging.info('did not find gwt version') 723 return True 724 725 logging.info('found gwt version %s', match.group(1)) 726 return match.group(1) != self.version 727 728 729 def _build_and_install(self, install_dir): 730 os.chdir(install_dir) 731 self._extract_compressed_package() 732 extracted_dir = self.local_filename[:-len('.zip')] 733 target_dir = os.path.join(install_dir, self.name) 734 if os.path.exists(target_dir): 735 shutil.rmtree(target_dir) 736 os.rename(extracted_dir, target_dir) 737 return True 738 739 740class GVizAPIPackage(ExternalPackage): 741 version = '1.7.0' 742 url_filename = 'gviz_api_py-%s.tar.gz' % version 743 local_filename = 'google-visualization-python.tar.gz' 744 urls = ('http://google-visualization-python.googlecode.com/files/%s' % ( 745 url_filename),) 746 hex_sum = 'cd9a0fb4ca5c4f86c0d85756f501fd54ccf492d2' 747 748 _build_and_install = ExternalPackage._build_and_install_from_package 749 _build_and_install_current_dir = ( 750 ExternalPackage._build_and_install_current_dir_noegg) 751