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