blob: c45230454ee3c4c7ed532426a94c0494e84d9840 [file] [log] [blame]
mblighfa29d662009-02-05 00:44:26 +00001#!/usr/bin/python
2#
3# Please keep this code python 2.4 compatible and stand alone.
4
5"""
6Fetch, build and install external Python library dependancies.
7
8This fetches external python libraries, builds them using your host's
9python and installs them under our own autotest/site-packages/ directory.
10
11Usage? Just run it.
12 utils/build_externals.py
13"""
14
15import compileall, logging, os, sha, shutil, sys, tempfile, time, urllib2
mbligh489e0e02009-06-08 16:46:11 +000016import subprocess, re
mblighe5699e22009-09-18 16:44:12 +000017import common
18from autotest_lib.client.common_lib import logging_config, logging_manager
mblighfa29d662009-02-05 00:44:26 +000019
20# Where package source be fetched to relative to the top of the autotest tree.
21PACKAGE_DIR = 'ExternalSource'
22
23# Where packages will be installed to relative to the top of the autotest tree.
24INSTALL_DIR = 'site-packages'
25
mbligh489e0e02009-06-08 16:46:11 +000026# Installs all packages, even if the system already has the version required
27INSTALL_ALL = False
28
mblighfa29d662009-02-05 00:44:26 +000029
30# Want to add more packages to fetch, build and install? See the class
31# definitions at the end of this file for examples of how to do it.
32
33
34_READ_SIZE = 64*1024
35_MAX_PACKAGE_SIZE = 100*1024*1024
36
37
mblighe5699e22009-09-18 16:44:12 +000038class BuildExternalsLoggingConfig(logging_config.LoggingConfig):
39 def configure_logging(self, results_dir=None, verbose=False):
40 super(BuildExternalsLoggingConfig, self).configure_logging(
41 use_console=True,
42 verbose=verbose)
43
mblighfa29d662009-02-05 00:44:26 +000044class Error(Exception):
45 """Local exception to be raised by code in this file."""
46
47class FetchError(Error):
48 """Failed to fetch a package from any of its listed URLs."""
49
50
51def main():
52 """
53 Find all ExternalPackage classes defined in this file and ask them to
54 fetch, build and install themselves.
55 """
mblighe5699e22009-09-18 16:44:12 +000056 logging_manager.configure_logging(BuildExternalsLoggingConfig(),
57 verbose=True)
mblighb155d0c2009-07-02 19:04:55 +000058 os.umask(022)
mblighfa29d662009-02-05 00:44:26 +000059
60 top_of_tree = _find_top_of_autotest_tree()
61 package_dir = os.path.join(top_of_tree, PACKAGE_DIR)
62 install_dir = os.path.join(top_of_tree, INSTALL_DIR)
63
lmr7ebba662009-10-23 12:55:09 +000064 # Make sure the install_dir is in our python module search path
65 # as well as the PYTHONPATH being used by all our setup.py
66 # install subprocesses.
mbligh623c5182009-02-07 01:31:31 +000067 if install_dir not in sys.path:
mbligh623c5182009-02-07 01:31:31 +000068 sys.path.insert(0, install_dir)
lmr7ebba662009-10-23 12:55:09 +000069 env_python_path_varname = 'PYTHONPATH'
70 env_python_path = os.environ.get(env_python_path_varname, '')
71 if install_dir+':' not in env_python_path:
72 os.environ[env_python_path_varname] = ':'.join([
73 install_dir, env_python_path])
mbligh623c5182009-02-07 01:31:31 +000074
mblighb155d0c2009-07-02 19:04:55 +000075 fetched_packages, fetch_errors = fetch_necessary_packages(package_dir,
76 install_dir)
mblighfa29d662009-02-05 00:44:26 +000077 install_errors = build_and_install_packages(fetched_packages, install_dir)
78
79 # Byte compile the code after it has been installed in its final
80 # location as .pyc files contain the path passed to compile_dir().
81 # When printing exception tracebacks, python uses that path first to look
82 # for the source code before checking the directory of the .pyc file.
83 # Don't leave references to our temporary build dir in the files.
mbligh623c5182009-02-07 01:31:31 +000084 logging.info('compiling .py files in %s to .pyc', install_dir)
85 compileall.compile_dir(install_dir, quiet=True)
86
87 # Some things install with whacky permissions, fix that.
88 system("chmod -R a+rX '%s'" % install_dir)
mblighfa29d662009-02-05 00:44:26 +000089
90 errors = fetch_errors + install_errors
91 for error_msg in errors:
92 print >>sys.stderr, error_msg
93
94 return len(errors)
95
96
mblighb155d0c2009-07-02 19:04:55 +000097def fetch_necessary_packages(dest_dir, install_dir):
mblighfa29d662009-02-05 00:44:26 +000098 """
99 Fetches all ExternalPackages into dest_dir.
100
mblighb155d0c2009-07-02 19:04:55 +0000101 @param dest_dir: Directory the packages should be fetched into.
102 @param install_dir: Directory where packages will later installed.
mblighfa29d662009-02-05 00:44:26 +0000103
104 @returns A tuple containing two lists:
105 * A list of ExternalPackage instances that were fetched and
106 need to be installed.
107 * A list of error messages for any failed fetches.
108 """
mbligh623c5182009-02-07 01:31:31 +0000109 names_to_check = sys.argv[1:]
mblighfa29d662009-02-05 00:44:26 +0000110 errors = []
111 fetched_packages = []
112 for package_class in ExternalPackage.subclasses:
113 package = package_class()
mbligh623c5182009-02-07 01:31:31 +0000114 if names_to_check and package.name.lower() not in names_to_check:
115 continue
mblighb155d0c2009-07-02 19:04:55 +0000116 if not package.is_needed(install_dir):
mblighfa29d662009-02-05 00:44:26 +0000117 logging.info('A new %s is not needed on this system.',
118 package.name)
mbligh489e0e02009-06-08 16:46:11 +0000119 if INSTALL_ALL:
120 logging.info('Installing anyways...')
121 else:
122 continue
mblighfa29d662009-02-05 00:44:26 +0000123 if not package.fetch(dest_dir):
124 msg = '!!! Unable to download %s' % package.name
125 print msg
126 errors.append(msg)
127 else:
128 fetched_packages.append(package)
129
130 return fetched_packages, errors
131
132
133def build_and_install_packages(packages, install_dir):
134 """
135 Builds and installs all packages into install_dir.
136
137 @param packages - A list of already fetched ExternalPackage instances.
138 @param install_dir - Directory the packages should be installed into.
139
140 @returns A list of error messages for any installs that failed.
141 """
142 errors = []
143 for package in packages:
144 if not package.build_and_install(install_dir):
145 msg = '!!! Unable to build and install %s' % package.name
146 print msg
147 errors.append(msg)
148 return errors
149
150
151def _checksum_file(full_path):
152 """@returns The hex checksum of a file given its pathname."""
153 inputfile = open(full_path, 'rb')
154 try:
155 hex_sum = sha.sha(inputfile.read()).hexdigest()
156 finally:
157 inputfile.close()
158 return hex_sum
159
160
161def _find_top_of_autotest_tree():
162 """@returns The full path to the top of the autotest directory tree."""
163 dirname = os.path.dirname(__file__)
164 autotest_dir = os.path.abspath(os.path.join(dirname, '..'))
165 return autotest_dir
166
167
168def system(commandline):
169 """Same as os.system(commandline) but logs the command first."""
170 logging.info(commandline)
171 return os.system(commandline)
172
173
174class ExternalPackage(object):
175 """
176 Defines an external package with URLs to fetch its sources from and
177 a build_and_install() method to unpack it, build it and install it
178 beneath our own autotest/site-packages directory.
179
180 Base Class. Subclass this to define packages.
181
182 Attributes:
183 @attribute urls - A tuple of URLs to try fetching the package from.
184 @attribute local_filename - A local filename to use when saving the
185 fetched package.
186 @attribute hex_sum - The hex digest (currently SHA1) of this package
187 to be used to verify its contents.
mbligh623c5182009-02-07 01:31:31 +0000188 @attribute module_name - The installed python module name to be used for
189 for a version check. Defaults to the lower case class name with
190 the word Package stripped off.
mblighf341fd12009-08-11 19:12:32 +0000191 @attribute version - The desired minimum package version.
192 @attribute os_requirements - A dictionary mapping a file pathname on the
193 the OS distribution to a likely name of a package the user
194 needs to install on their system in order to get this file.
mblighfa29d662009-02-05 00:44:26 +0000195 @attribute name - Read only, the printable name of the package.
196 @attribute subclasses - This class attribute holds a list of all defined
197 subclasses. It is constructed dynamically using the metaclass.
198 """
199 subclasses = []
200 urls = ()
201 local_filename = None
202 hex_sum = None
mbligh623c5182009-02-07 01:31:31 +0000203 module_name = None
204 version = None
mblighf341fd12009-08-11 19:12:32 +0000205 os_requirements = None
mblighfa29d662009-02-05 00:44:26 +0000206
207
208 class __metaclass__(type):
209 """Any time a subclass is defined, add it to our list."""
mbligh623c5182009-02-07 01:31:31 +0000210 def __init__(mcs, name, bases, dict):
mblighfa29d662009-02-05 00:44:26 +0000211 if name != 'ExternalPackage':
mbligh623c5182009-02-07 01:31:31 +0000212 mcs.subclasses.append(mcs)
mblighfa29d662009-02-05 00:44:26 +0000213
214
215 def __init__(self):
216 self.verified_package = ''
mbligh623c5182009-02-07 01:31:31 +0000217 if not self.module_name:
218 self.module_name = self.name.lower()
219 self.installed_version = ''
mblighfa29d662009-02-05 00:44:26 +0000220
221
222 @property
223 def name(self):
224 """Return the class name with any trailing 'Package' stripped off."""
225 class_name = self.__class__.__name__
226 if class_name.endswith('Package'):
227 return class_name[:-len('Package')]
228 return class_name
229
230
mblighb155d0c2009-07-02 19:04:55 +0000231 def is_needed(self, unused_install_dir):
mbligh623c5182009-02-07 01:31:31 +0000232 """@returns True if self.module_name needs to be built and installed."""
233 if not self.module_name or not self.version:
234 logging.warning('version and module_name required for '
235 'is_needed() check to work.')
236 return True
237 try:
238 module = __import__(self.module_name)
239 except ImportError, e:
240 logging.info('Could not import %s.', self.module_name)
241 return True
242 self.installed_version = self._get_installed_version_from_module(module)
243 logging.info('imported %s version %s.', self.module_name,
244 self.installed_version)
245 return self.version > self.installed_version
246
247
248 def _get_installed_version_from_module(self, module):
249 """Ask our module its version string and return it or '' if unknown."""
250 try:
251 return module.__version__
252 except AttributeError:
253 logging.error('could not get version from %s', module)
254 return ''
255
256
mblighfa29d662009-02-05 00:44:26 +0000257 def _build_and_install(self, install_dir):
258 """Subclasses MUST provide their own implementation."""
259 raise NotImplementedError
260
261
262 def _build_and_install_current_dir(self, install_dir):
263 """
mblighdb530cc2009-08-24 22:19:13 +0000264 Subclasses that use _build_and_install_from_package() MUST provide
mblighfa29d662009-02-05 00:44:26 +0000265 their own implementation of this method.
266 """
267 raise NotImplementedError
268
269
270 def build_and_install(self, install_dir):
271 """
272 Builds and installs the package. It must have been fetched already.
273
274 @param install_dir - The package installation directory. If it does
275 not exist it will be created.
276 """
277 if not self.verified_package:
278 raise Error('Must call fetch() first. - %s' % self.name)
mblighf341fd12009-08-11 19:12:32 +0000279 self._check_os_requirements()
mblighfa29d662009-02-05 00:44:26 +0000280 return self._build_and_install(install_dir)
281
282
mblighf341fd12009-08-11 19:12:32 +0000283 def _check_os_requirements(self):
284 if not self.os_requirements:
285 return
286 failed = False
287 for file_name, package_name in self.os_requirements.iteritems():
288 if not os.path.exists(file_name):
289 failed = True
290 logging.error('File %s not found, %s needs it.',
291 file_name, self.name)
292 logging.error('Perhaps you need to install something similar '
293 'to the %s package for OS first.', package_name)
294 if failed:
295 raise Error('Missing OS requirements for %s. (see above)' %
296 self.name)
297
298
mbligh623c5182009-02-07 01:31:31 +0000299 def _build_and_install_current_dir_setup_py(self, install_dir):
300 """For use as a _build_and_install_current_dir implementation."""
301 egg_path = self._build_egg_using_setup_py(setup_py='setup.py')
302 if not egg_path:
303 return False
304 return self._install_from_egg(install_dir, egg_path)
305
306
307 def _build_and_install_current_dir_setupegg_py(self, install_dir):
308 """For use as a _build_and_install_current_dir implementation."""
309 egg_path = self._build_egg_using_setup_py(setup_py='setupegg.py')
310 if not egg_path:
311 return False
312 return self._install_from_egg(install_dir, egg_path)
313
314
mbligh36cec882009-08-24 22:02:13 +0000315 def _build_and_install_current_dir_noegg(self, install_dir):
316 if not self._build_using_setup_py():
317 return False
318 return self._install_using_setup_py_and_rsync(install_dir)
319
320
mblighdb530cc2009-08-24 22:19:13 +0000321 def _build_and_install_from_package(self, install_dir):
mblighfa29d662009-02-05 00:44:26 +0000322 """
323 This method may be used as a _build_and_install() implementation
324 for subclasses if they implement _build_and_install_current_dir().
325
326 Extracts the .tar.gz file, chdirs into the extracted directory
327 (which is assumed to match the tar filename) and calls
328 _build_and_isntall_current_dir from there.
329
330 Afterwards the build (regardless of failure) extracted .tar.gz
331 directory is cleaned up.
332
333 @returns True on success, False otherwise.
334
335 @raises OSError If the expected extraction directory does not exist.
336 """
mblighdb530cc2009-08-24 22:19:13 +0000337 self._extract_compressed_package()
338 if self.verified_package.endswith('.tar.gz'):
339 extension = '.tar.gz'
340 elif self.verified_package.endswith('.tar.bz2'):
341 extension = '.tar.bz2'
342 elif self.verified_package.endswith('.zip'):
343 extension = '.zip'
344 else:
345 raise Error('Unexpected package file extension on %s' %
346 self.verified_package)
mblighfa29d662009-02-05 00:44:26 +0000347 os.chdir(os.path.dirname(self.verified_package))
mblighdb530cc2009-08-24 22:19:13 +0000348 os.chdir(self.local_filename[:-len(extension)])
mblighfa29d662009-02-05 00:44:26 +0000349 extracted_dir = os.getcwd()
350 try:
351 return self._build_and_install_current_dir(install_dir)
352 finally:
353 os.chdir(os.path.join(extracted_dir, '..'))
354 shutil.rmtree(extracted_dir)
355
356
mblighdb530cc2009-08-24 22:19:13 +0000357 def _extract_compressed_package(self):
358 """Extract the fetched compressed .tar or .zip within its directory."""
mblighfa29d662009-02-05 00:44:26 +0000359 if not self.verified_package:
360 raise Error('Package must have been fetched first.')
361 os.chdir(os.path.dirname(self.verified_package))
mblighb155d0c2009-07-02 19:04:55 +0000362 if self.verified_package.endswith('gz'):
363 status = system("tar -xzf '%s'" % self.verified_package)
364 elif self.verified_package.endswith('bz2'):
365 status = system("tar -xjf '%s'" % self.verified_package)
mblighdb530cc2009-08-24 22:19:13 +0000366 elif self.verified_package.endswith('zip'):
367 status = system("unzip '%s'" % self.verified_package)
mblighb155d0c2009-07-02 19:04:55 +0000368 else:
369 raise Error('Unknown compression suffix on %s.' %
370 self.verified_package)
mblighfa29d662009-02-05 00:44:26 +0000371 if status:
372 raise Error('tar failed with %s' % (status,))
373
374
mbligh623c5182009-02-07 01:31:31 +0000375 def _build_using_setup_py(self, setup_py='setup.py'):
mblighfa29d662009-02-05 00:44:26 +0000376 """
377 Assuming the cwd is the extracted python package, execute a simple
378 python setup.py build.
379
mbligh623c5182009-02-07 01:31:31 +0000380 @param setup_py - The name of the setup.py file to execute.
381
mblighfa29d662009-02-05 00:44:26 +0000382 @returns True on success, False otherwise.
383 """
mbligh623c5182009-02-07 01:31:31 +0000384 if not os.path.exists(setup_py):
385 raise Error('%sdoes not exist in %s' % (setup_py, os.getcwd()))
386 status = system("'%s' %s build" % (sys.executable, setup_py))
mblighfa29d662009-02-05 00:44:26 +0000387 if status:
388 logging.error('%s build failed.' % self.name)
389 return False
390 return True
391
392
mbligh623c5182009-02-07 01:31:31 +0000393 def _build_egg_using_setup_py(self, setup_py='setup.py'):
mblighfa29d662009-02-05 00:44:26 +0000394 """
395 Assuming the cwd is the extracted python package, execute a simple
396 python setup.py bdist_egg.
397
mbligh623c5182009-02-07 01:31:31 +0000398 @param setup_py - The name of the setup.py file to execute.
399
mblighfa29d662009-02-05 00:44:26 +0000400 @returns The relative path to the resulting egg file or '' on failure.
401 """
mbligh623c5182009-02-07 01:31:31 +0000402 if not os.path.exists(setup_py):
403 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
mblighfa29d662009-02-05 00:44:26 +0000404 egg_subdir = 'dist'
405 if os.path.isdir(egg_subdir):
406 shutil.rmtree(egg_subdir)
mbligh623c5182009-02-07 01:31:31 +0000407 status = system("'%s' %s bdist_egg" % (sys.executable, setup_py))
mblighfa29d662009-02-05 00:44:26 +0000408 if status:
409 logging.error('bdist_egg of setuptools failed.')
410 return ''
411 # I've never seen a bdist_egg lay multiple .egg files.
412 for filename in os.listdir(egg_subdir):
413 if filename.endswith('.egg'):
414 return os.path.join(egg_subdir, filename)
415
416
417 def _install_from_egg(self, install_dir, egg_path):
418 """
419 Install a module from an egg file by unzipping the necessary parts
420 into install_dir.
421
422 @param install_dir - The installation directory.
423 @param egg_path - The pathname of the egg file.
424 """
425 status = system("unzip -q -o -d '%s' '%s'" % (install_dir, egg_path))
426 if status:
427 logging.error('unzip of %s failed', egg_path)
428 return False
429 egg_info = os.path.join(install_dir, 'EGG-INFO')
430 if os.path.isdir(egg_info):
431 shutil.rmtree(egg_info)
432 return True
433
434
mbligh623c5182009-02-07 01:31:31 +0000435 def _install_using_setup_py_and_rsync(self, install_dir,
436 setup_py='setup.py'):
mblighfa29d662009-02-05 00:44:26 +0000437 """
438 Assuming the cwd is the extracted python package, execute a simple:
439
440 python setup.py install --prefix=BLA
441
442 BLA will be a temporary directory that everything installed will
443 be picked out of and rsynced to the appropriate place under
444 install_dir afterwards.
445
446 Afterwards, it deconstructs the extra lib/pythonX.Y/site-packages/
447 directory tree that setuptools created and moves all installed
448 site-packages directly up into install_dir itself.
449
450 @param install_dir the directory for the install to happen under.
mbligh623c5182009-02-07 01:31:31 +0000451 @param setup_py - The name of the setup.py file to execute.
mblighfa29d662009-02-05 00:44:26 +0000452
453 @returns True on success, False otherwise.
454 """
mbligh623c5182009-02-07 01:31:31 +0000455 if not os.path.exists(setup_py):
456 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
mblighfa29d662009-02-05 00:44:26 +0000457
458 temp_dir = tempfile.mkdtemp(dir='/var/tmp')
459 try:
mbligh623c5182009-02-07 01:31:31 +0000460 status = system("'%s' %s install --no-compile --prefix='%s'"
461 % (sys.executable, setup_py, temp_dir))
mblighfa29d662009-02-05 00:44:26 +0000462 if status:
mbligh623c5182009-02-07 01:31:31 +0000463 logging.error('%s install failed.' % self.name)
mblighfa29d662009-02-05 00:44:26 +0000464 return False
465
mblighfa29d662009-02-05 00:44:26 +0000466 # This makes assumptions about what python setup.py install
467 # does when given a prefix. Is this always correct?
468 python_xy = 'python%s' % sys.version[:3]
469 if os.path.isdir(os.path.join(temp_dir, 'lib')):
470 # NOTE: This ignores anything outside of the lib/ dir that
471 # was installed.
472 temp_site_dir = os.path.join(
473 temp_dir, 'lib', python_xy, 'site-packages')
474 else:
475 temp_site_dir = temp_dir
476
477 status = system("rsync -r '%s/' '%s/'" %
478 (temp_site_dir, install_dir))
479 if status:
480 logging.error('%s rsync to install_dir failed.' % self.name)
481 return False
482 return True
483 finally:
484 shutil.rmtree(temp_dir)
485
486
487
488 def fetch(self, dest_dir):
489 """
490 Fetch the package from one its URLs and save it in dest_dir.
491
492 If the the package already exists in dest_dir and the checksum
493 matches this code will not fetch it again.
494
495 Sets the 'verified_package' attribute with the destination pathname.
496
497 @param dest_dir - The destination directory to save the local file.
498 If it does not exist it will be created.
499
500 @returns A boolean indicating if we the package is now in dest_dir.
501 @raises FetchError - When something unexpected happens.
502 """
503 if not os.path.exists(dest_dir):
504 os.makedirs(dest_dir)
505 local_path = os.path.join(dest_dir, self.local_filename)
506
507 # If the package exists, verify its checksum and be happy if it is good.
508 if os.path.exists(local_path):
509 actual_hex_sum = _checksum_file(local_path)
510 if self.hex_sum == actual_hex_sum:
511 logging.info('Good checksum for existing %s package.',
512 self.name)
513 self.verified_package = local_path
514 return True
515 logging.warning('Bad checksum for existing %s package. '
516 'Re-downloading', self.name)
517 os.rename(local_path, local_path + '.wrong-checksum')
518
519 # Download the package from one of its urls, rejecting any if the
520 # checksum does not match.
521 for url in self.urls:
522 logging.info('Fetching %s', url)
523 try:
524 url_file = urllib2.urlopen(url)
mblighb155d0c2009-07-02 19:04:55 +0000525 except (urllib2.URLError, EnvironmentError):
mblighfa29d662009-02-05 00:44:26 +0000526 logging.warning('Could not fetch %s package from %s.',
527 self.name, url)
528 continue
529 data_length = int(url_file.info().get('Content-Length',
530 _MAX_PACKAGE_SIZE))
531 if data_length <= 0 or data_length > _MAX_PACKAGE_SIZE:
532 raise FetchError('%s from %s fails Content-Length %d '
533 'sanity check.' % (self.name, url,
534 data_length))
535 checksum = sha.sha()
536 total_read = 0
537 output = open(local_path, 'wb')
538 try:
539 while total_read < data_length:
540 data = url_file.read(_READ_SIZE)
541 if not data:
542 break
543 output.write(data)
544 checksum.update(data)
545 total_read += len(data)
546 finally:
547 output.close()
548 if self.hex_sum != checksum.hexdigest():
549 logging.warning('Bad checksum for %s fetched from %s.',
550 self.name, url)
551 os.unlink(local_path)
552 continue
553 logging.info('Good checksum.')
554 self.verified_package = local_path
555 return True
556 else:
557 return False
558
559
mbligh623c5182009-02-07 01:31:31 +0000560# NOTE: This class definition must come -before- all other ExternalPackage
mblighfa29d662009-02-05 00:44:26 +0000561# classes that need to use this version of setuptools so that is is inserted
562# into the ExternalPackage.subclasses list before them.
563class SetuptoolsPackage(ExternalPackage):
mbligh623c5182009-02-07 01:31:31 +0000564 # For all known setuptools releases a string compare works for the
565 # version string. Hopefully they never release a 0.10. (Their own
566 # version comparison code would break if they did.)
mblighfa29d662009-02-05 00:44:26 +0000567 version = '0.6c9'
568 urls = ('http://pypi.python.org/packages/source/s/setuptools/'
569 'setuptools-%s.tar.gz' % (version,),)
570 local_filename = 'setuptools-%s.tar.gz' % version
571 hex_sum = '79086433b341f0c1df45e10d586a7d3cc25089f1'
572
573 SUDO_SLEEP_DELAY = 15
574
575
mblighfa29d662009-02-05 00:44:26 +0000576 def _build_and_install(self, install_dir):
577 """Install setuptools on the system."""
578 logging.info('NOTE: setuptools install does not use install_dir.')
mblighdb530cc2009-08-24 22:19:13 +0000579 return self._build_and_install_from_package(install_dir)
mblighfa29d662009-02-05 00:44:26 +0000580
581
582 def _build_and_install_current_dir(self, install_dir):
583 egg_path = self._build_egg_using_setup_py()
584 if not egg_path:
585 return False
586
587 print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n'
588 print 'About to run sudo to install setuptools', self.version
589 print 'on your system for use by', sys.executable, '\n'
590 print '!! ^C within', self.SUDO_SLEEP_DELAY, 'seconds to abort.\n'
591 time.sleep(self.SUDO_SLEEP_DELAY)
592
593 # Copy the egg to the local filesystem /var/tmp so that root can
594 # access it properly (avoid NFS squashroot issues).
595 temp_dir = tempfile.mkdtemp(dir='/var/tmp')
596 try:
597 shutil.copy(egg_path, temp_dir)
598 egg_name = os.path.split(egg_path)[1]
599 temp_egg = os.path.join(temp_dir, egg_name)
mbligh489e0e02009-06-08 16:46:11 +0000600 p = subprocess.Popen(['sudo', '/bin/sh', temp_egg],
601 stdout=subprocess.PIPE)
602 regex = re.compile('Copying (.*?) to (.*?)\n')
603 match = regex.search(p.communicate()[0])
604 status = p.wait()
605
606 if match:
607 compiled = os.path.join(match.group(2), match.group(1))
608 os.system("sudo chmod a+r '%s'" % compiled)
mblighfa29d662009-02-05 00:44:26 +0000609 finally:
610 shutil.rmtree(temp_dir)
611
612 if status:
613 logging.error('install of setuptools from egg failed.')
614 return False
615 return True
616
617
618class MySQLdbPackage(ExternalPackage):
mbligh623c5182009-02-07 01:31:31 +0000619 module_name = 'MySQLdb'
mblighfa29d662009-02-05 00:44:26 +0000620 version = '1.2.2'
mbligh279e5332009-07-28 23:32:11 +0000621 urls = ('http://downloads.sourceforge.net/project/mysql-python/'
622 'mysql-python/%(version)s/MySQL-python-%(version)s.tar.gz'
623 % dict(version=version),)
mblighfa29d662009-02-05 00:44:26 +0000624 local_filename = 'MySQL-python-%s.tar.gz' % version
625 hex_sum = '945a04773f30091ad81743f9eb0329a3ee3de383'
626
mbligh623c5182009-02-07 01:31:31 +0000627 _build_and_install_current_dir = (
628 ExternalPackage._build_and_install_current_dir_setup_py)
mblighfa29d662009-02-05 00:44:26 +0000629
630
mbligh3916b262009-06-08 16:36:35 +0000631 def _build_and_install(self, install_dir):
632 if not os.path.exists('/usr/bin/mysql_config'):
633 logging.error('You need to install /usr/bin/mysql_config')
634 logging.error('On Ubuntu or Debian based systems use this: '
mblighfd387072009-09-03 20:46:23 +0000635 'sudo apt-get install libmysqlclient15-dev')
mbligh3916b262009-06-08 16:36:35 +0000636 return False
mblighdb530cc2009-08-24 22:19:13 +0000637 return self._build_and_install_from_package(install_dir)
mbligh3916b262009-06-08 16:36:35 +0000638
639
mblighfa29d662009-02-05 00:44:26 +0000640class DjangoPackage(ExternalPackage):
mbligh623c5182009-02-07 01:31:31 +0000641 version = '1.0.2'
642 local_filename = 'Django-%s-final.tar.gz' % version
643 urls = ('http://media.djangoproject.com/releases/%s/%s'
644 % (version, local_filename),)
mblighfa29d662009-02-05 00:44:26 +0000645 hex_sum = 'f2d9088f17aff47ea17e5767740cab67b2a73b6b'
646
mblighdb530cc2009-08-24 22:19:13 +0000647 _build_and_install = ExternalPackage._build_and_install_from_package
mbligh36cec882009-08-24 22:02:13 +0000648 _build_and_install_current_dir = (
649 ExternalPackage._build_and_install_current_dir_noegg)
mblighfa29d662009-02-05 00:44:26 +0000650
651
mbligh623c5182009-02-07 01:31:31 +0000652 def _get_installed_version_from_module(self, module):
mblighb155d0c2009-07-02 19:04:55 +0000653 try:
654 return module.get_version().split()[0]
655 except AttributeError:
656 return '0.9.6'
mbligh623c5182009-02-07 01:31:31 +0000657
658
mblighfa29d662009-02-05 00:44:26 +0000659
mbligh623c5182009-02-07 01:31:31 +0000660class NumpyPackage(ExternalPackage):
661 version = '1.2.1'
662 local_filename = 'numpy-%s.tar.gz' % version
mbligh279e5332009-07-28 23:32:11 +0000663 urls = ('http://downloads.sourceforge.net/project/numpy/NumPy/%(version)s/'
664 'numpy-%(version)s.tar.gz' % dict(version=version),)
mbligh623c5182009-02-07 01:31:31 +0000665 hex_sum = '1aa706e733aea18eaffa70d93c0105718acb66c5'
666
mblighdb530cc2009-08-24 22:19:13 +0000667 _build_and_install = ExternalPackage._build_and_install_from_package
mbligh623c5182009-02-07 01:31:31 +0000668 _build_and_install_current_dir = (
669 ExternalPackage._build_and_install_current_dir_setupegg_py)
670
671
672# This requires numpy so it must be declared after numpy to guarantee that it
673# is already installed.
674class MatplotlibPackage(ExternalPackage):
mbligh279e5332009-07-28 23:32:11 +0000675 version = '0.98.5.3'
676 short_version = '0.98.5'
mbligh623c5182009-02-07 01:31:31 +0000677 local_filename = 'matplotlib-%s.tar.gz' % version
mbligh279e5332009-07-28 23:32:11 +0000678 urls = ('http://downloads.sourceforge.net/project/matplotlib/matplotlib/'
679 'matplotlib-%s/matplotlib-%s.tar.gz' % (short_version, version),)
680 hex_sum = '2f6c894cf407192b3b60351bcc6468c0385d47b6'
mblighf341fd12009-08-11 19:12:32 +0000681 os_requirements = {'/usr/include/ft2build.h': 'libfreetype6-dev',
682 '/usr/include/png.h': 'libpng12-dev'}
mbligh623c5182009-02-07 01:31:31 +0000683
mblighdb530cc2009-08-24 22:19:13 +0000684 _build_and_install = ExternalPackage._build_and_install_from_package
mbligh623c5182009-02-07 01:31:31 +0000685 _build_and_install_current_dir = (
686 ExternalPackage._build_and_install_current_dir_setupegg_py)
687
688
mblighdb530cc2009-08-24 22:19:13 +0000689class AtForkPackage(ExternalPackage):
mblighbeb687d2009-09-03 20:18:41 +0000690 version = '0.1.2'
mblighdb530cc2009-08-24 22:19:13 +0000691 local_filename = 'atfork-%s.zip' % version
692 urls = ('http://python-atfork.googlecode.com/files/' + local_filename,)
mblighbeb687d2009-09-03 20:18:41 +0000693 hex_sum = '5baa64c73e966b57fa797040585c760c502dc70b'
mblighdb530cc2009-08-24 22:19:13 +0000694
695 _build_and_install = ExternalPackage._build_and_install_from_package
696 _build_and_install_current_dir = (
697 ExternalPackage._build_and_install_current_dir_noegg)
698
699
mblighb155d0c2009-07-02 19:04:55 +0000700class ParamikoPackage(ExternalPackage):
mblighf51b2892009-08-24 22:03:09 +0000701 version = '1.7.5'
mblighb155d0c2009-07-02 19:04:55 +0000702 local_filename = 'paramiko-%s.tar.gz' % version
703 urls = ('http://www.lag.net/paramiko/download/' + local_filename,)
mblighf51b2892009-08-24 22:03:09 +0000704 hex_sum = '592be7a08290070b71da63a8e6f28a803399e5c5'
mblighb155d0c2009-07-02 19:04:55 +0000705
706
mblighdb530cc2009-08-24 22:19:13 +0000707 _build_and_install = ExternalPackage._build_and_install_from_package
mblighb155d0c2009-07-02 19:04:55 +0000708
709
710 def _check_for_pycrypto(self):
711 # NOTE(gps): Linux distros have better python-crypto packages than we
712 # can easily get today via a wget due to the library's age and staleness
713 # yet many security and behavior bugs are fixed by patches that distros
714 # already apply. PyCrypto has a new active maintainer in 2009. Once a
715 # new release is made (http://pycrypto.org/) we should add an installer.
716 try:
717 import Crypto
718 except ImportError:
719 logging.error('Please run "sudo apt-get install python-crypto" '
720 'or your Linux distro\'s equivalent.')
721 return False
722 return True
723
724
725 def _build_and_install_current_dir(self, install_dir):
726 if not self._check_for_pycrypto():
727 return False
728 # paramiko 1.7.4 doesn't require building, it is just a module directory
729 # that we can rsync into place directly.
730 if not os.path.isdir('paramiko'):
731 raise Error('no paramiko directory in %s.' % os.getcwd())
732 status = system("rsync -r 'paramiko' '%s/'" % install_dir)
733 if status:
734 logging.error('%s rsync to install_dir failed.' % self.name)
735 return False
736 return True
737
738
739class GwtPackage(ExternalPackage):
740 """Fetch and extract a local copy of GWT used to build the frontend."""
741
showard61364422009-09-09 15:31:30 +0000742 version = '1.7.0'
mblighb155d0c2009-07-02 19:04:55 +0000743 local_filename = 'gwt-linux-%s.tar.bz2' % version
744 urls = ('http://google-web-toolkit.googlecode.com/files/' + local_filename,)
showard61364422009-09-09 15:31:30 +0000745 hex_sum = 'accb39506e1fa719ba166cf54451c91dafd9d456'
mblighb155d0c2009-07-02 19:04:55 +0000746 name = 'gwt'
showard61364422009-09-09 15:31:30 +0000747 about_filename = 'about.txt'
mblighb155d0c2009-07-02 19:04:55 +0000748 module_name = None # Not a Python module.
749
750
751 def is_needed(self, install_dir):
showard61364422009-09-09 15:31:30 +0000752 gwt_dir = os.path.join(install_dir, self.name)
753 about_file = os.path.join(install_dir, self.name, self.about_filename)
754
755 if not os.path.exists(gwt_dir) or not os.path.exists(about_file):
756 logging.info('gwt not installed for autotest')
757 return True
758
759 f = open(about_file, 'r')
760 version_line = f.readline()
761 f.close()
762
763 match = re.match(r'Google Web Toolkit (.*)', version_line)
764 if not match:
765 logging.info('did not find gwt version')
766 return True
767
768 logging.info('found gwt version %s', match.group(1))
769 return match.group(1) != self.version
mblighb155d0c2009-07-02 19:04:55 +0000770
771
772 def build_and_install(self, install_dir):
773 os.chdir(install_dir)
mblighdb530cc2009-08-24 22:19:13 +0000774 self._extract_compressed_package()
mblighb155d0c2009-07-02 19:04:55 +0000775 extracted_dir = self.local_filename[:-len('.tar.bz2')]
776 target_dir = os.path.join(install_dir, self.name)
showard61364422009-09-09 15:31:30 +0000777 if os.path.exists(target_dir):
778 shutil.rmtree(target_dir)
mblighb155d0c2009-07-02 19:04:55 +0000779 os.rename(extracted_dir, target_dir)
780 return True
781
782
mblighfa29d662009-02-05 00:44:26 +0000783if __name__ == '__main__':
784 sys.exit(main())