blob: 9587786c7be67648a5775e0546315e437c9beeca [file] [log] [blame]
jamesrenff6e5aa2010-02-12 00:46:40 +00001# Please keep this code python 2.4 compatible and stand alone.
2
3import logging, os, shutil, sys, tempfile, time, urllib2
4import subprocess, re
Scott James Remnantbb1a9672014-03-03 14:03:09 -08005from autotest_lib.client.common_lib import autotemp, revision_control, utils
jamesrenff6e5aa2010-02-12 00:46:40 +00006
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):
Dan Shi57631dc2013-02-22 09:54:14 -080029 """Same as os.system(commandline) but logs the command first.
30
31 @param commandline: commandline to be called.
32 """
jamesrenff6e5aa2010-02-12 00:46:40 +000033 logging.info(commandline)
34 return os.system(commandline)
35
36
37def find_top_of_autotest_tree():
38 """@returns The full path to the top of the autotest directory tree."""
39 dirname = os.path.dirname(__file__)
40 autotest_dir = os.path.abspath(os.path.join(dirname, '..'))
41 return autotest_dir
42
43
44class ExternalPackage(object):
45 """
46 Defines an external package with URLs to fetch its sources from and
47 a build_and_install() method to unpack it, build it and install it
48 beneath our own autotest/site-packages directory.
49
50 Base Class. Subclass this to define packages.
beepsd5335852013-04-09 09:58:52 -070051 Note: Unless your subclass has a specific reason to, it should not
52 re-install the package every time build_externals is invoked, as this
53 happens periodically through the scheduler. To avoid doing so the is_needed
54 method needs to return an appropriate value.
jamesrenff6e5aa2010-02-12 00:46:40 +000055
56 Attributes:
57 @attribute urls - A tuple of URLs to try fetching the package from.
58 @attribute local_filename - A local filename to use when saving the
59 fetched package.
60 @attribute hex_sum - The hex digest (currently SHA1) of this package
61 to be used to verify its contents.
62 @attribute module_name - The installed python module name to be used for
63 for a version check. Defaults to the lower case class name with
64 the word Package stripped off.
65 @attribute version - The desired minimum package version.
Michael Janssena7427612014-11-14 15:44:39 -080066 @attribute os_requirements - A dictionary mapping pathname tuples on the
jamesrenff6e5aa2010-02-12 00:46:40 +000067 the OS distribution to a likely name of a package the user
68 needs to install on their system in order to get this file.
Michael Janssena7427612014-11-14 15:44:39 -080069 One of the files in the tuple must exist.
jamesrenff6e5aa2010-02-12 00:46:40 +000070 @attribute name - Read only, the printable name of the package.
71 @attribute subclasses - This class attribute holds a list of all defined
72 subclasses. It is constructed dynamically using the metaclass.
73 """
74 subclasses = []
75 urls = ()
76 local_filename = None
77 hex_sum = None
78 module_name = None
79 version = None
80 os_requirements = None
81
82
83 class __metaclass__(type):
84 """Any time a subclass is defined, add it to our list."""
85 def __init__(mcs, name, bases, dict):
beepsd9153b52013-01-23 20:52:46 -080086 if name != 'ExternalPackage' and not name.startswith('_'):
jamesrenff6e5aa2010-02-12 00:46:40 +000087 mcs.subclasses.append(mcs)
88
89
90 def __init__(self):
91 self.verified_package = ''
92 if not self.module_name:
93 self.module_name = self.name.lower()
94 self.installed_version = ''
95
96
97 @property
98 def name(self):
99 """Return the class name with any trailing 'Package' stripped off."""
100 class_name = self.__class__.__name__
101 if class_name.endswith('Package'):
102 return class_name[:-len('Package')]
103 return class_name
104
105
Dan Shi7b6297b2015-06-23 13:54:09 -0700106 def is_needed(self, install_dir):
beepsd5335852013-04-09 09:58:52 -0700107 """
108 Check to see if we need to reinstall a package. This is contingent on:
109 1. Module name: If the name of the module is different from the package,
110 the class that installs it needs to specify a module_name string,
111 so we can try importing the module.
112
113 2. Installed version: If the module doesn't contain a __version__ the
114 class that installs it needs to override the
115 _get_installed_version_from_module method to return an appropriate
116 version string.
117
118 3. Version/Minimum version: The class that installs the package should
119 contain a version string, and an optional minimum version string.
Dan Shi57631dc2013-02-22 09:54:14 -0800120
Dan Shi7b6297b2015-06-23 13:54:09 -0700121 4. install_dir: If the module exists in a different directory, e.g.,
122 /usr/lib/python2.7/dist-packages/, the module will be forced to be
123 installed in install_dir.
124
125 @param install_dir: install directory.
beepsd5335852013-04-09 09:58:52 -0700126 @returns True if self.module_name needs to be built and installed.
Dan Shi57631dc2013-02-22 09:54:14 -0800127 """
jamesrenff6e5aa2010-02-12 00:46:40 +0000128 if not self.module_name or not self.version:
129 logging.warning('version and module_name required for '
130 'is_needed() check to work.')
131 return True
132 try:
133 module = __import__(self.module_name)
134 except ImportError, e:
jamesrenca2a9002010-04-20 22:33:22 +0000135 logging.info("%s isn't present. Will install.", self.module_name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000136 return True
Dan Shi7b6297b2015-06-23 13:54:09 -0700137 if not module.__file__.startswith(install_dir):
138 logging.info('Module %s is installed in %s, rather than %s. The '
139 'module will be forced to be installed in %s.',
140 self.module_name, module.__file__, install_dir,
141 install_dir)
142 return True
jamesrenff6e5aa2010-02-12 00:46:40 +0000143 self.installed_version = self._get_installed_version_from_module(module)
144 logging.info('imported %s version %s.', self.module_name,
145 self.installed_version)
Dale Curtis74a314b2011-06-23 14:55:46 -0700146 if hasattr(self, 'minimum_version'):
147 return self.minimum_version > self.installed_version
148 else:
149 return self.version > self.installed_version
jamesrenff6e5aa2010-02-12 00:46:40 +0000150
151
152 def _get_installed_version_from_module(self, module):
153 """Ask our module its version string and return it or '' if unknown."""
154 try:
155 return module.__version__
156 except AttributeError:
157 logging.error('could not get version from %s', module)
158 return ''
159
160
161 def _build_and_install(self, install_dir):
162 """Subclasses MUST provide their own implementation."""
163 raise NotImplementedError
164
165
166 def _build_and_install_current_dir(self, install_dir):
167 """
168 Subclasses that use _build_and_install_from_package() MUST provide
169 their own implementation of this method.
170 """
171 raise NotImplementedError
172
173
174 def build_and_install(self, install_dir):
175 """
176 Builds and installs the package. It must have been fetched already.
177
178 @param install_dir - The package installation directory. If it does
179 not exist it will be created.
180 """
181 if not self.verified_package:
182 raise Error('Must call fetch() first. - %s' % self.name)
183 self._check_os_requirements()
184 return self._build_and_install(install_dir)
185
186
187 def _check_os_requirements(self):
188 if not self.os_requirements:
189 return
190 failed = False
Michael Janssena7427612014-11-14 15:44:39 -0800191 for file_names, package_name in self.os_requirements.iteritems():
192 if not any(os.path.exists(file_name) for file_name in file_names):
jamesrenff6e5aa2010-02-12 00:46:40 +0000193 failed = True
Michael Janssena7427612014-11-14 15:44:39 -0800194 logging.error('Can\'t find %s, %s probably needs it.',
J. Richard Barnette6f7606b2015-06-11 17:26:23 -0700195 ' or '.join(file_names), self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000196 logging.error('Perhaps you need to install something similar '
197 'to the %s package for OS first.', package_name)
198 if failed:
199 raise Error('Missing OS requirements for %s. (see above)' %
200 self.name)
201
202
203 def _build_and_install_current_dir_setup_py(self, install_dir):
204 """For use as a _build_and_install_current_dir implementation."""
205 egg_path = self._build_egg_using_setup_py(setup_py='setup.py')
206 if not egg_path:
207 return False
208 return self._install_from_egg(install_dir, egg_path)
209
210
211 def _build_and_install_current_dir_setupegg_py(self, install_dir):
212 """For use as a _build_and_install_current_dir implementation."""
213 egg_path = self._build_egg_using_setup_py(setup_py='setupegg.py')
214 if not egg_path:
215 return False
216 return self._install_from_egg(install_dir, egg_path)
217
218
219 def _build_and_install_current_dir_noegg(self, install_dir):
220 if not self._build_using_setup_py():
221 return False
222 return self._install_using_setup_py_and_rsync(install_dir)
223
224
225 def _build_and_install_from_package(self, install_dir):
226 """
227 This method may be used as a _build_and_install() implementation
228 for subclasses if they implement _build_and_install_current_dir().
229
230 Extracts the .tar.gz file, chdirs into the extracted directory
231 (which is assumed to match the tar filename) and calls
232 _build_and_isntall_current_dir from there.
233
234 Afterwards the build (regardless of failure) extracted .tar.gz
235 directory is cleaned up.
236
237 @returns True on success, False otherwise.
238
239 @raises OSError If the expected extraction directory does not exist.
240 """
241 self._extract_compressed_package()
242 if self.verified_package.endswith('.tar.gz'):
243 extension = '.tar.gz'
244 elif self.verified_package.endswith('.tar.bz2'):
245 extension = '.tar.bz2'
246 elif self.verified_package.endswith('.zip'):
247 extension = '.zip'
248 else:
249 raise Error('Unexpected package file extension on %s' %
250 self.verified_package)
251 os.chdir(os.path.dirname(self.verified_package))
252 os.chdir(self.local_filename[:-len(extension)])
253 extracted_dir = os.getcwd()
254 try:
255 return self._build_and_install_current_dir(install_dir)
256 finally:
257 os.chdir(os.path.join(extracted_dir, '..'))
258 shutil.rmtree(extracted_dir)
259
260
261 def _extract_compressed_package(self):
262 """Extract the fetched compressed .tar or .zip within its directory."""
263 if not self.verified_package:
264 raise Error('Package must have been fetched first.')
265 os.chdir(os.path.dirname(self.verified_package))
266 if self.verified_package.endswith('gz'):
267 status = system("tar -xzf '%s'" % self.verified_package)
268 elif self.verified_package.endswith('bz2'):
269 status = system("tar -xjf '%s'" % self.verified_package)
270 elif self.verified_package.endswith('zip'):
271 status = system("unzip '%s'" % self.verified_package)
272 else:
273 raise Error('Unknown compression suffix on %s.' %
274 self.verified_package)
275 if status:
276 raise Error('tar failed with %s' % (status,))
277
278
279 def _build_using_setup_py(self, setup_py='setup.py'):
280 """
281 Assuming the cwd is the extracted python package, execute a simple
282 python setup.py build.
283
284 @param setup_py - The name of the setup.py file to execute.
285
286 @returns True on success, False otherwise.
287 """
288 if not os.path.exists(setup_py):
289 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
290 status = system("'%s' %s build" % (sys.executable, setup_py))
291 if status:
Dan Shi57631dc2013-02-22 09:54:14 -0800292 logging.error('%s build failed.', self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000293 return False
294 return True
295
296
297 def _build_egg_using_setup_py(self, setup_py='setup.py'):
298 """
299 Assuming the cwd is the extracted python package, execute a simple
300 python setup.py bdist_egg.
301
302 @param setup_py - The name of the setup.py file to execute.
303
304 @returns The relative path to the resulting egg file or '' on failure.
305 """
306 if not os.path.exists(setup_py):
307 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
308 egg_subdir = 'dist'
309 if os.path.isdir(egg_subdir):
310 shutil.rmtree(egg_subdir)
311 status = system("'%s' %s bdist_egg" % (sys.executable, setup_py))
312 if status:
313 logging.error('bdist_egg of setuptools failed.')
314 return ''
315 # I've never seen a bdist_egg lay multiple .egg files.
316 for filename in os.listdir(egg_subdir):
317 if filename.endswith('.egg'):
318 return os.path.join(egg_subdir, filename)
319
320
321 def _install_from_egg(self, install_dir, egg_path):
322 """
323 Install a module from an egg file by unzipping the necessary parts
324 into install_dir.
325
326 @param install_dir - The installation directory.
327 @param egg_path - The pathname of the egg file.
328 """
329 status = system("unzip -q -o -d '%s' '%s'" % (install_dir, egg_path))
330 if status:
331 logging.error('unzip of %s failed', egg_path)
332 return False
333 egg_info = os.path.join(install_dir, 'EGG-INFO')
334 if os.path.isdir(egg_info):
335 shutil.rmtree(egg_info)
336 return True
337
338
339 def _get_temp_dir(self):
340 return tempfile.mkdtemp(dir='/var/tmp')
341
342
343 def _site_packages_path(self, temp_dir):
344 # This makes assumptions about what python setup.py install
345 # does when given a prefix. Is this always correct?
346 python_xy = 'python%s' % sys.version[:3]
347 return os.path.join(temp_dir, 'lib', python_xy, 'site-packages')
348
349
beepsd9153b52013-01-23 20:52:46 -0800350 def _rsync (self, temp_site_dir, install_dir):
351 """Rsync contents. """
352 status = system("rsync -r '%s/' '%s/'" %
353 (os.path.normpath(temp_site_dir),
354 os.path.normpath(install_dir)))
355 if status:
356 logging.error('%s rsync to install_dir failed.', self.name)
357 return False
358 return True
359
360
jamesrenff6e5aa2010-02-12 00:46:40 +0000361 def _install_using_setup_py_and_rsync(self, install_dir,
362 setup_py='setup.py',
363 temp_dir=None):
364 """
365 Assuming the cwd is the extracted python package, execute a simple:
366
367 python setup.py install --prefix=BLA
368
369 BLA will be a temporary directory that everything installed will
370 be picked out of and rsynced to the appropriate place under
371 install_dir afterwards.
372
373 Afterwards, it deconstructs the extra lib/pythonX.Y/site-packages/
374 directory tree that setuptools created and moves all installed
375 site-packages directly up into install_dir itself.
376
377 @param install_dir the directory for the install to happen under.
378 @param setup_py - The name of the setup.py file to execute.
379
380 @returns True on success, False otherwise.
381 """
382 if not os.path.exists(setup_py):
383 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
384
385 if temp_dir is None:
386 temp_dir = self._get_temp_dir()
387
388 try:
389 status = system("'%s' %s install --no-compile --prefix='%s'"
390 % (sys.executable, setup_py, temp_dir))
391 if status:
Dan Shi57631dc2013-02-22 09:54:14 -0800392 logging.error('%s install failed.', self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000393 return False
394
395 if os.path.isdir(os.path.join(temp_dir, 'lib')):
396 # NOTE: This ignores anything outside of the lib/ dir that
397 # was installed.
398 temp_site_dir = self._site_packages_path(temp_dir)
399 else:
400 temp_site_dir = temp_dir
401
beepsd9153b52013-01-23 20:52:46 -0800402 return self._rsync(temp_site_dir, install_dir)
jamesrenff6e5aa2010-02-12 00:46:40 +0000403 finally:
404 shutil.rmtree(temp_dir)
405
406
407
408 def _build_using_make(self, install_dir):
409 """Build the current package using configure/make.
410
411 @returns True on success, False otherwise.
412 """
413 install_prefix = os.path.join(install_dir, 'usr', 'local')
414 status = system('./configure --prefix=%s' % install_prefix)
415 if status:
416 logging.error('./configure failed for %s', self.name)
417 return False
418 status = system('make')
419 if status:
420 logging.error('make failed for %s', self.name)
421 return False
422 status = system('make check')
423 if status:
424 logging.error('make check failed for %s', self.name)
425 return False
426 return True
427
428
429 def _install_using_make(self):
430 """Install the current package using make install.
431
432 Assumes the install path was set up while running ./configure (in
433 _build_using_make()).
434
435 @returns True on success, False otherwise.
436 """
437 status = system('make install')
438 return status == 0
439
440
441 def fetch(self, dest_dir):
442 """
443 Fetch the package from one its URLs and save it in dest_dir.
444
445 If the the package already exists in dest_dir and the checksum
446 matches this code will not fetch it again.
447
448 Sets the 'verified_package' attribute with the destination pathname.
449
450 @param dest_dir - The destination directory to save the local file.
451 If it does not exist it will be created.
452
453 @returns A boolean indicating if we the package is now in dest_dir.
454 @raises FetchError - When something unexpected happens.
455 """
456 if not os.path.exists(dest_dir):
457 os.makedirs(dest_dir)
458 local_path = os.path.join(dest_dir, self.local_filename)
459
460 # If the package exists, verify its checksum and be happy if it is good.
461 if os.path.exists(local_path):
462 actual_hex_sum = _checksum_file(local_path)
463 if self.hex_sum == actual_hex_sum:
464 logging.info('Good checksum for existing %s package.',
465 self.name)
466 self.verified_package = local_path
467 return True
468 logging.warning('Bad checksum for existing %s package. '
469 'Re-downloading', self.name)
470 os.rename(local_path, local_path + '.wrong-checksum')
471
472 # Download the package from one of its urls, rejecting any if the
473 # checksum does not match.
474 for url in self.urls:
475 logging.info('Fetching %s', url)
476 try:
477 url_file = urllib2.urlopen(url)
478 except (urllib2.URLError, EnvironmentError):
479 logging.warning('Could not fetch %s package from %s.',
480 self.name, url)
481 continue
Dan Shi57631dc2013-02-22 09:54:14 -0800482
jamesrenff6e5aa2010-02-12 00:46:40 +0000483 data_length = int(url_file.info().get('Content-Length',
484 _MAX_PACKAGE_SIZE))
485 if data_length <= 0 or data_length > _MAX_PACKAGE_SIZE:
486 raise FetchError('%s from %s fails Content-Length %d '
487 'sanity check.' % (self.name, url,
488 data_length))
489 checksum = utils.hash('sha1')
490 total_read = 0
491 output = open(local_path, 'wb')
492 try:
493 while total_read < data_length:
494 data = url_file.read(_READ_SIZE)
495 if not data:
496 break
497 output.write(data)
498 checksum.update(data)
499 total_read += len(data)
500 finally:
501 output.close()
502 if self.hex_sum != checksum.hexdigest():
503 logging.warning('Bad checksum for %s fetched from %s.',
504 self.name, url)
505 logging.warning('Got %s', checksum.hexdigest())
506 logging.warning('Expected %s', self.hex_sum)
507 os.unlink(local_path)
508 continue
509 logging.info('Good checksum.')
510 self.verified_package = local_path
511 return True
512 else:
513 return False
514
515
516# NOTE: This class definition must come -before- all other ExternalPackage
517# classes that need to use this version of setuptools so that is is inserted
518# into the ExternalPackage.subclasses list before them.
519class SetuptoolsPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800520 """setuptools package"""
jamesrenff6e5aa2010-02-12 00:46:40 +0000521 # For all known setuptools releases a string compare works for the
522 # version string. Hopefully they never release a 0.10. (Their own
523 # version comparison code would break if they did.)
Dale Curtis74a314b2011-06-23 14:55:46 -0700524 # Any system with setuptools > 0.6 is fine. If none installed, then
525 # try to install the latest found on the upstream.
526 minimum_version = '0.6'
Eric Li6f27d4f2010-09-29 10:55:17 -0700527 version = '0.6c11'
jamesrenff6e5aa2010-02-12 00:46:40 +0000528 urls = ('http://pypi.python.org/packages/source/s/setuptools/'
529 'setuptools-%s.tar.gz' % (version,),)
530 local_filename = 'setuptools-%s.tar.gz' % version
Eric Li6f27d4f2010-09-29 10:55:17 -0700531 hex_sum = '8d1ad6384d358c547c50c60f1bfdb3362c6c4a7d'
jamesrenff6e5aa2010-02-12 00:46:40 +0000532
533 SUDO_SLEEP_DELAY = 15
534
535
536 def _build_and_install(self, install_dir):
537 """Install setuptools on the system."""
538 logging.info('NOTE: setuptools install does not use install_dir.')
539 return self._build_and_install_from_package(install_dir)
540
541
542 def _build_and_install_current_dir(self, install_dir):
543 egg_path = self._build_egg_using_setup_py()
544 if not egg_path:
545 return False
546
547 print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n'
548 print 'About to run sudo to install setuptools', self.version
549 print 'on your system for use by', sys.executable, '\n'
550 print '!! ^C within', self.SUDO_SLEEP_DELAY, 'seconds to abort.\n'
551 time.sleep(self.SUDO_SLEEP_DELAY)
552
553 # Copy the egg to the local filesystem /var/tmp so that root can
554 # access it properly (avoid NFS squashroot issues).
555 temp_dir = self._get_temp_dir()
556 try:
557 shutil.copy(egg_path, temp_dir)
558 egg_name = os.path.split(egg_path)[1]
559 temp_egg = os.path.join(temp_dir, egg_name)
560 p = subprocess.Popen(['sudo', '/bin/sh', temp_egg],
561 stdout=subprocess.PIPE)
562 regex = re.compile('Copying (.*?) to (.*?)\n')
563 match = regex.search(p.communicate()[0])
564 status = p.wait()
565
566 if match:
567 compiled = os.path.join(match.group(2), match.group(1))
568 os.system("sudo chmod a+r '%s'" % compiled)
569 finally:
570 shutil.rmtree(temp_dir)
571
572 if status:
573 logging.error('install of setuptools from egg failed.')
574 return False
575 return True
576
577
578class MySQLdbPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800579 """mysql package, used in scheduler."""
jamesrenff6e5aa2010-02-12 00:46:40 +0000580 module_name = 'MySQLdb'
Alex Miller47d61282013-04-17 13:53:58 -0700581 version = '1.2.3'
jamesrenff6e5aa2010-02-12 00:46:40 +0000582 urls = ('http://downloads.sourceforge.net/project/mysql-python/'
583 'mysql-python/%(version)s/MySQL-python-%(version)s.tar.gz'
584 % dict(version=version),)
585 local_filename = 'MySQL-python-%s.tar.gz' % version
Alex Miller47d61282013-04-17 13:53:58 -0700586 hex_sum = '3511bb8c57c6016eeafa531d5c3ea4b548915e3c'
jamesrenff6e5aa2010-02-12 00:46:40 +0000587
588 _build_and_install_current_dir = (
589 ExternalPackage._build_and_install_current_dir_setup_py)
590
591
592 def _build_and_install(self, install_dir):
593 if not os.path.exists('/usr/bin/mysql_config'):
594 logging.error('You need to install /usr/bin/mysql_config')
595 logging.error('On Ubuntu or Debian based systems use this: '
596 'sudo apt-get install libmysqlclient15-dev')
597 return False
598 return self._build_and_install_from_package(install_dir)
599
600
601class DjangoPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800602 """django package."""
Alex Miller47d61282013-04-17 13:53:58 -0700603 version = '1.5.1'
jamesrenff6e5aa2010-02-12 00:46:40 +0000604 local_filename = 'Django-%s.tar.gz' % version
605 urls = ('http://www.djangoproject.com/download/%s/tarball/' % version,)
Alex Miller47d61282013-04-17 13:53:58 -0700606 hex_sum = '0ab97b90c4c79636e56337f426f1e875faccbba1'
jamesrenff6e5aa2010-02-12 00:46:40 +0000607
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
613 def _get_installed_version_from_module(self, module):
614 try:
615 return module.get_version().split()[0]
616 except AttributeError:
617 return '0.9.6'
618
619
620
621class NumpyPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800622 """numpy package, required by matploglib."""
Scott Zawalskic2c9a012013-03-06 09:13:26 -0500623 version = '1.7.0'
jamesrenff6e5aa2010-02-12 00:46:40 +0000624 local_filename = 'numpy-%s.tar.gz' % version
625 urls = ('http://downloads.sourceforge.net/project/numpy/NumPy/%(version)s/'
626 'numpy-%(version)s.tar.gz' % dict(version=version),)
Scott Zawalskic2c9a012013-03-06 09:13:26 -0500627 hex_sum = 'ba328985f20390b0f969a5be2a6e1141d5752cf9'
jamesrenff6e5aa2010-02-12 00:46:40 +0000628
629 _build_and_install = ExternalPackage._build_and_install_from_package
630 _build_and_install_current_dir = (
631 ExternalPackage._build_and_install_current_dir_setupegg_py)
632
633
jamesrenff6e5aa2010-02-12 00:46:40 +0000634class MatplotlibPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800635 """
636 matplotlib package
637
638 This requires numpy so it must be declared after numpy to guarantee that
639 it is already installed.
640 """
jamesrenff6e5aa2010-02-12 00:46:40 +0000641 version = '0.98.5.3'
642 short_version = '0.98.5'
643 local_filename = 'matplotlib-%s.tar.gz' % version
644 urls = ('http://downloads.sourceforge.net/project/matplotlib/matplotlib/'
645 'matplotlib-%s/matplotlib-%s.tar.gz' % (short_version, version),)
646 hex_sum = '2f6c894cf407192b3b60351bcc6468c0385d47b6'
Michael Janssena7427612014-11-14 15:44:39 -0800647 os_requirements = {('/usr/include/freetype2/ft2build.h',
648 '/usr/include/ft2build.h'): 'libfreetype6-dev',
649 ('/usr/include/png.h'): 'libpng12-dev'}
jamesrenff6e5aa2010-02-12 00:46:40 +0000650
651 _build_and_install = ExternalPackage._build_and_install_from_package
652 _build_and_install_current_dir = (
653 ExternalPackage._build_and_install_current_dir_setupegg_py)
654
655
656class AtForkPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800657 """atfork package"""
jamesrenff6e5aa2010-02-12 00:46:40 +0000658 version = '0.1.2'
659 local_filename = 'atfork-%s.zip' % version
660 urls = ('http://python-atfork.googlecode.com/files/' + local_filename,)
661 hex_sum = '5baa64c73e966b57fa797040585c760c502dc70b'
662
663 _build_and_install = ExternalPackage._build_and_install_from_package
664 _build_and_install_current_dir = (
665 ExternalPackage._build_and_install_current_dir_noegg)
666
667
668class ParamikoPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800669 """paramiko package"""
jamesrenff6e5aa2010-02-12 00:46:40 +0000670 version = '1.7.5'
671 local_filename = 'paramiko-%s.tar.gz' % version
Eric Li861b2d52011-02-04 14:50:35 -0800672 urls = ('http://www.lag.net/paramiko/download/' + local_filename,
673 'ftp://mirrors.kernel.org/gentoo/distfiles/' + local_filename,)
jamesrenff6e5aa2010-02-12 00:46:40 +0000674 hex_sum = '592be7a08290070b71da63a8e6f28a803399e5c5'
675
676
677 _build_and_install = ExternalPackage._build_and_install_from_package
678
679
680 def _check_for_pycrypto(self):
681 # NOTE(gps): Linux distros have better python-crypto packages than we
682 # can easily get today via a wget due to the library's age and staleness
683 # yet many security and behavior bugs are fixed by patches that distros
684 # already apply. PyCrypto has a new active maintainer in 2009. Once a
685 # new release is made (http://pycrypto.org/) we should add an installer.
686 try:
687 import Crypto
688 except ImportError:
689 logging.error('Please run "sudo apt-get install python-crypto" '
690 'or your Linux distro\'s equivalent.')
691 return False
692 return True
693
694
695 def _build_and_install_current_dir(self, install_dir):
696 if not self._check_for_pycrypto():
697 return False
698 # paramiko 1.7.4 doesn't require building, it is just a module directory
699 # that we can rsync into place directly.
700 if not os.path.isdir('paramiko'):
701 raise Error('no paramiko directory in %s.' % os.getcwd())
702 status = system("rsync -r 'paramiko' '%s/'" % install_dir)
703 if status:
Dan Shi57631dc2013-02-22 09:54:14 -0800704 logging.error('%s rsync to install_dir failed.', self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000705 return False
706 return True
707
708
Chris Masonebafbbb02012-05-16 13:41:36 -0700709class RequestsPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800710 """requests package"""
Chris Masonebafbbb02012-05-16 13:41:36 -0700711 version = '0.11.2'
712 local_filename = 'requests-%s.tar.gz' % version
713 urls = ('http://pypi.python.org/packages/source/r/requests/' +
714 local_filename,)
715 hex_sum = '00a49e8bd6dd8955acf6f6269d1b85f50c70b712'
716
717 _build_and_install = ExternalPackage._build_and_install_from_package
718 _build_and_install_current_dir = (
719 ExternalPackage._build_and_install_current_dir_setup_py)
720
721
beeps32a63082013-08-22 14:02:29 -0700722class JsonRPCLib(ExternalPackage):
723 """jsonrpclib package"""
724 version = '0.1.3'
725 module_name = 'jsonrpclib'
726 local_filename = '%s-%s.tar.gz' % (module_name, version)
727 urls = ('http://pypi.python.org/packages/source/j/%s/%s' %
728 (module_name, local_filename), )
729 hex_sum = '431714ed19ab677f641ce5d678a6a95016f5c452'
730
731 def _get_installed_version_from_module(self, module):
732 # jsonrpclib doesn't contain a proper version
733 return self.version
734
735 _build_and_install = ExternalPackage._build_and_install_from_package
736 _build_and_install_current_dir = (
737 ExternalPackage._build_and_install_current_dir_noegg)
738
739
jamesrenff6e5aa2010-02-12 00:46:40 +0000740class Httplib2Package(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800741 """httplib2 package"""
jamesrenff6e5aa2010-02-12 00:46:40 +0000742 version = '0.6.0'
743 local_filename = 'httplib2-%s.tar.gz' % version
744 urls = ('http://httplib2.googlecode.com/files/' + local_filename,)
745 hex_sum = '995344b2704826cc0d61a266e995b328d92445a5'
746
747 def _get_installed_version_from_module(self, module):
748 # httplib2 doesn't contain a proper version
749 return self.version
750
751 _build_and_install = ExternalPackage._build_and_install_from_package
752 _build_and_install_current_dir = (
753 ExternalPackage._build_and_install_current_dir_noegg)
754
755
756class GwtPackage(ExternalPackage):
757 """Fetch and extract a local copy of GWT used to build the frontend."""
758
Dale Curtis74a314b2011-06-23 14:55:46 -0700759 version = '2.3.0'
jamesren012d0322010-04-30 20:21:29 +0000760 local_filename = 'gwt-%s.zip' % version
jamesrenff6e5aa2010-02-12 00:46:40 +0000761 urls = ('http://google-web-toolkit.googlecode.com/files/' + local_filename,)
Dale Curtis74a314b2011-06-23 14:55:46 -0700762 hex_sum = 'd51fce9166e6b31349659ffca89baf93e39bc84b'
jamesrenff6e5aa2010-02-12 00:46:40 +0000763 name = 'gwt'
764 about_filename = 'about.txt'
765 module_name = None # Not a Python module.
766
767
768 def is_needed(self, install_dir):
769 gwt_dir = os.path.join(install_dir, self.name)
770 about_file = os.path.join(install_dir, self.name, self.about_filename)
771
772 if not os.path.exists(gwt_dir) or not os.path.exists(about_file):
773 logging.info('gwt not installed for autotest')
774 return True
775
776 f = open(about_file, 'r')
777 version_line = f.readline()
778 f.close()
779
780 match = re.match(r'Google Web Toolkit (.*)', version_line)
781 if not match:
782 logging.info('did not find gwt version')
783 return True
784
785 logging.info('found gwt version %s', match.group(1))
786 return match.group(1) != self.version
787
788
jamesrenca2a9002010-04-20 22:33:22 +0000789 def _build_and_install(self, install_dir):
jamesrenff6e5aa2010-02-12 00:46:40 +0000790 os.chdir(install_dir)
791 self._extract_compressed_package()
jamesren012d0322010-04-30 20:21:29 +0000792 extracted_dir = self.local_filename[:-len('.zip')]
jamesrenff6e5aa2010-02-12 00:46:40 +0000793 target_dir = os.path.join(install_dir, self.name)
794 if os.path.exists(target_dir):
795 shutil.rmtree(target_dir)
796 os.rename(extracted_dir, target_dir)
797 return True
798
799
Mike Trutyddd44b22011-04-14 15:38:56 -0700800class GVizAPIPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800801 """gviz package"""
Alex Miller0e1217c2013-02-22 10:11:08 -0800802 module_name = 'gviz_api'
Mike Trutyddd44b22011-04-14 15:38:56 -0700803 version = '1.7.0'
804 url_filename = 'gviz_api_py-%s.tar.gz' % version
805 local_filename = 'google-visualization-python.tar.gz'
806 urls = ('http://google-visualization-python.googlecode.com/files/%s' % (
807 url_filename),)
808 hex_sum = 'cd9a0fb4ca5c4f86c0d85756f501fd54ccf492d2'
809
810 _build_and_install = ExternalPackage._build_and_install_from_package
811 _build_and_install_current_dir = (
812 ExternalPackage._build_and_install_current_dir_noegg)
beepsd9153b52013-01-23 20:52:46 -0800813
Alex Miller0e1217c2013-02-22 10:11:08 -0800814 def _get_installed_version_from_module(self, module):
815 # gviz doesn't contain a proper version
816 return self.version
817
beepsd9153b52013-01-23 20:52:46 -0800818
Scott Zawalski2a6acf82013-02-11 16:18:27 -0500819class StatsdPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800820 """python-statsd package"""
Alex Milleree10e932013-03-18 18:01:46 -0700821 version = '1.5.8'
Scott Zawalski2a6acf82013-02-11 16:18:27 -0500822 url_filename = 'python-statsd-%s.tar.gz' % version
823 local_filename = url_filename
824 urls = ('http://pypi.python.org/packages/source/p/python-statsd/%s' % (
825 url_filename),)
Alex Milleree10e932013-03-18 18:01:46 -0700826 hex_sum = '50eccab74ca88884297954497f85039e5a2e732c'
Scott Zawalski2a6acf82013-02-11 16:18:27 -0500827
828 _build_and_install = ExternalPackage._build_and_install_from_package
829 _build_and_install_current_dir = (
830 ExternalPackage._build_and_install_current_dir_setup_py)
831
832
beeps93bef482013-02-04 16:24:22 -0800833class GdataPackage(ExternalPackage):
834 """
835 Pulls the GData library, giving us an API to query tracker.
836 """
837
838 version = '2.0.14'
839 url_filename = 'gdata-%s.tar.gz' % version
840 local_filename = url_filename
841 urls = ('http://gdata-python-client.googlecode.com/files/%s' % (
842 url_filename),)
843 hex_sum = '5eed0e01ab931e3f706ec544fc8f06ecac384e91'
844
845 _build_and_install = ExternalPackage._build_and_install_from_package
846 _build_and_install_current_dir = (
847 ExternalPackage._build_and_install_current_dir_noegg)
848
Alex Miller0e1217c2013-02-22 10:11:08 -0800849 def _get_installed_version_from_module(self, module):
850 # gdata doesn't contain a proper version
851 return self.version
852
beeps93bef482013-02-04 16:24:22 -0800853
beepse0db09d2013-02-24 17:13:21 -0800854class GoogleAPIClientPackage(ExternalPackage):
855 """
856 Pulls the Python Google API client library.
857 """
858 version = '1.1'
beepsd5335852013-04-09 09:58:52 -0700859 module_name = 'apiclient'
beepse0db09d2013-02-24 17:13:21 -0800860 url_filename = 'google-api-python-client-%s.tar.gz' % version
861 local_filename = url_filename
862 urls = ('https://google-api-python-client.googlecode.com/files/%s' % (
863 url_filename),)
864 hex_sum = '2294949683e367b3d4ecaeb77502509c5af21e60'
865
866 _build_and_install = ExternalPackage._build_and_install_from_package
867 _build_and_install_current_dir = (
868 ExternalPackage._build_and_install_current_dir_setup_py)
869
870
beepsd5335852013-04-09 09:58:52 -0700871class GFlagsPackage(ExternalPackage):
872 """
873 Gets the Python GFlags client library.
874 """
875 # gflags doesn't contain a proper version
876 version = '2.0'
877 url_filename = 'python-gflags-%s.tar.gz' % version
878 local_filename = url_filename
879 urls = ('https://python-gflags.googlecode.com/files/%s' % (
880 url_filename),)
881 hex_sum = 'db309e6964b102ff36de319ce551db512a78281e'
882
883 _build_and_install = ExternalPackage._build_and_install_from_package
884 _build_and_install_current_dir = (
885 ExternalPackage._build_and_install_current_dir_setup_py)
886
887
888 def _get_installed_version_from_module(self, module):
889 return self.version
890
891
Alex Millere7b6e8b2013-02-13 17:34:04 -0800892class DnsPythonPackage(ExternalPackage):
893 """
894 dns module
895
896 Used in unittests.
897 """
898 module_name = 'dns'
899 version = '1.3.5'
900 url_filename = 'dnspython-%s.tar.gz' % version
901 local_filename = url_filename
902 urls = ('http://www.dnspython.org/kits/%s/%s' % (
Dan Shi57631dc2013-02-22 09:54:14 -0800903 version, url_filename),)
904
Alex Millere7b6e8b2013-02-13 17:34:04 -0800905 hex_sum = '06314dad339549613435470c6add992910e26e5d'
906
907 _build_and_install = ExternalPackage._build_and_install_from_package
908 _build_and_install_current_dir = (
909 ExternalPackage._build_and_install_current_dir_noegg)
910
911 def _get_installed_version_from_module(self, module):
912 """Ask our module its version string and return it or '' if unknown."""
913 try:
914 __import__(self.module_name + '.version')
915 return module.version.version
916 except AttributeError:
917 logging.error('could not get version from %s', module)
918 return ''
919
920
921class PyudevPackage(ExternalPackage):
922 """
923 pyudev module
924
925 Used in unittests.
926 """
927 version = '0.16.1'
928 url_filename = 'pyudev-%s.tar.gz' % version
929 local_filename = url_filename
930 urls = ('http://pypi.python.org/packages/source/p/pyudev/%s' % (
931 url_filename),)
932 hex_sum = 'b36bc5c553ce9b56d32a5e45063a2c88156771c0'
933
934 _build_and_install = ExternalPackage._build_and_install_from_package
935 _build_and_install_current_dir = (
936 ExternalPackage._build_and_install_current_dir_setup_py)
937
938
939class PyMoxPackage(ExternalPackage):
940 """
941 mox module
942
943 Used in unittests.
944 """
945 module_name = 'mox'
946 version = '0.5.3'
947 url_filename = 'mox-%s.tar.gz' % version
948 local_filename = url_filename
949 urls = ('http://pypi.python.org/packages/source/m/mox/%s' % (
950 url_filename),)
951 hex_sum = '1c502d2c0a8aefbba2c7f385a83d33e7d822452a'
952
953 _build_and_install = ExternalPackage._build_and_install_from_package
954 _build_and_install_current_dir = (
955 ExternalPackage._build_and_install_current_dir_noegg)
956
957 def _get_installed_version_from_module(self, module):
958 # mox doesn't contain a proper version
959 return self.version
960
961
Jason Abele2371d1a2013-11-22 12:18:18 -0800962class PySeleniumPackage(ExternalPackage):
963 """
964 selenium module
965
966 Used in wifi_interop suite.
967 """
968 module_name = 'selenium'
969 version = '2.37.2'
970 url_filename = 'selenium-%s.tar.gz' % version
971 local_filename = url_filename
972 urls = ('https://pypi.python.org/packages/source/s/selenium/%s' % (
973 url_filename),)
974 hex_sum = '66946d5349e36d946daaad625c83c30c11609e36'
975
976 _build_and_install = ExternalPackage._build_and_install_from_package
977 _build_and_install_current_dir = (
978 ExternalPackage._build_and_install_current_dir_setup_py)
979
980
Simran Basid6b83772014-01-06 16:31:30 -0800981class FaultHandlerPackage(ExternalPackage):
982 """
983 faulthandler module
984 """
985 module_name = 'faulthandler'
986 version = '2.3'
987 url_filename = '%s-%s.tar.gz' % (module_name, version)
988 local_filename = url_filename
989 urls = ('http://pypi.python.org/packages/source/f/faulthandler/%s' %
990 (url_filename),)
991 hex_sum = 'efb30c068414fba9df892e48fcf86170cbf53589'
992
993 _build_and_install = ExternalPackage._build_and_install_from_package
994 _build_and_install_current_dir = (
995 ExternalPackage._build_and_install_current_dir_noegg)
996
997
Alex Millera87e5482014-06-09 16:04:49 -0700998class PsutilPackage(ExternalPackage):
999 """
1000 psutil module
1001 """
1002 module_name = 'psutil'
1003 version = '2.1.1'
1004 url_filename = '%s-%s.tar.gz' % (module_name, version)
1005 local_filename = url_filename
1006 urls = ('http://pypi.python.org/packages/source/p/psutil/%s' %
1007 (url_filename),)
1008 hex_sum = '0c20a20ed316e69f2b0881530439213988229916'
1009
1010 _build_and_install = ExternalPackage._build_and_install_from_package
1011 _build_and_install_current_dir = (
1012 ExternalPackage._build_and_install_current_dir_setup_py)
1013
1014
Michael Liangd2d294c2014-06-24 15:24:49 -07001015class ElasticSearchPackage(ExternalPackage):
1016 """elasticsearch-py package."""
1017 version = '1.0.0'
1018 url_filename = 'elasticsearch-%s.tar.gz' % version
1019 local_filename = url_filename
Michael Liang5934b712014-08-12 15:45:14 -07001020 urls = ('https://pypi.python.org/packages/source/e/elasticsearch/%s' %
1021 (url_filename),)
Michael Liangd2d294c2014-06-24 15:24:49 -07001022 hex_sum = 'e53e93eb2729c1dcd1bc3453d22340314027e900'
1023 _build_and_install = ExternalPackage._build_and_install_from_package
1024 _build_and_install_current_dir = (
1025 ExternalPackage._build_and_install_current_dir_setup_py)
1026
1027
Michael Liang5934b712014-08-12 15:45:14 -07001028class Urllib3Package(ExternalPackage):
1029 """elasticsearch-py package."""
1030 version = '1.9'
1031 url_filename = 'urllib3-%s.tar.gz' % version
1032 local_filename = url_filename
1033 urls = ('https://pypi.python.org/packages/source/u/urllib3/%s' %
1034 (url_filename),)
1035 hex_sum = '9522197efb2a2b49ce804de3a515f06d97b6602f'
1036 _build_and_install = ExternalPackage._build_and_install_from_package
1037 _build_and_install_current_dir = (
1038 ExternalPackage._build_and_install_current_dir_setup_py)
1039
1040
J. Richard Barnette428b3442014-07-22 11:38:55 -07001041class ImagingLibraryPackage(ExternalPackage):
1042 """Python Imaging Library (PIL)."""
1043 version = '1.1.7'
1044 url_filename = 'Imaging-%s.tar.gz' % version
1045 local_filename = url_filename
1046 urls = ('http://effbot.org/downloads/%s' % url_filename,)
1047 hex_sum = '76c37504251171fda8da8e63ecb8bc42a69a5c81'
Dan Shi2d9a28c2015-03-13 21:20:32 -07001048 # The path of zlib library might be different from what PIL setup.py is
1049 # expected. Following change does the best attempt to link the library
1050 # to a path PIL setup.py will try.
1051 libz_possible_path = '/usr/lib/x86_64-linux-gnu/libz.so'
1052 libz_expected_path = '/usr/lib/libz.so'
1053 if (os.path.exists(libz_possible_path) and
1054 not os.path.exists(libz_expected_path)):
1055 utils.run('sudo ln -s %s %s' % (libz_possible_path, libz_expected_path))
J. Richard Barnette428b3442014-07-22 11:38:55 -07001056 _build_and_install = ExternalPackage._build_and_install_from_package
1057 _build_and_install_current_dir = (
1058 ExternalPackage._build_and_install_current_dir_noegg)
1059
1060
beepsd9153b52013-01-23 20:52:46 -08001061class _ExternalGitRepo(ExternalPackage):
1062 """
1063 Parent class for any package which needs to pull a git repo.
1064
1065 This class inherits from ExternalPackage only so we can sync git
1066 repos through the build_externals script. We do not reuse any of
1067 ExternalPackage's other methods. Any package that needs a git repo
1068 should subclass this and override build_and_install or fetch as
1069 they see appropriate.
1070 """
1071
Michael Janssena7427612014-11-14 15:44:39 -08001072 os_requirements = {('/usr/bin/git') : 'git-core'}
beepsd9153b52013-01-23 20:52:46 -08001073
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001074 # All the chromiumos projects used on the lab servers should have a 'prod'
1075 # branch used to track the software version deployed in prod.
1076 PROD_BRANCH = 'prod'
1077
beepsd9153b52013-01-23 20:52:46 -08001078 def is_needed(self, unused_install_dir):
1079 """Tell build_externals that we need to fetch."""
1080 # TODO(beeps): check if we're already upto date.
1081 return True
1082
1083
1084 def build_and_install(self, unused_install_dir):
1085 """
1086 Fall through method to install a package.
1087
1088 Overwritten in base classes to pull a git repo.
1089 """
1090 raise NotImplementedError
1091
1092
1093 def fetch(self, unused_dest_dir):
1094 """Fallthrough method to fetch a package."""
1095 return True
1096
1097
1098class HdctoolsRepo(_ExternalGitRepo):
1099 """Clones or updates the hdctools repo."""
1100
Alex Miller0e1217c2013-02-22 10:11:08 -08001101 module_name = 'servo'
beepsd9153b52013-01-23 20:52:46 -08001102 temp_hdctools_dir = tempfile.mktemp(suffix='hdctools')
Alex Miller9fbe67f2013-09-06 15:04:48 -07001103 _GIT_URL = ('https://chromium.googlesource.com/'
beepsd9153b52013-01-23 20:52:46 -08001104 'chromiumos/third_party/hdctools')
1105
1106 def fetch(self, unused_dest_dir):
1107 """
1108 Fetch repo to a temporary location.
1109
1110 We use an intermediate temp directory to stage our
1111 installation because we only care about the servo package.
1112 If we can't get at the top commit hash after fetching
1113 something is wrong. This can happen when we've cloned/pulled
1114 an empty repo. Not something we expect to do.
1115
1116 @parma unused_dest_dir: passed in because we inherit from
1117 ExternalPackage.
1118
1119 @return: True if repo sync was successful.
1120 """
1121 git_repo = revision_control.GitRepo(
1122 self.temp_hdctools_dir,
1123 self._GIT_URL,
1124 None,
beepsaae3f1c2013-03-19 15:49:14 -07001125 abs_work_tree=self.temp_hdctools_dir)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001126 git_repo.reinit_repo_at(self.PROD_BRANCH)
beepsd9153b52013-01-23 20:52:46 -08001127
1128 if git_repo.get_latest_commit_hash():
1129 return True
1130 return False
1131
1132
1133 def build_and_install(self, install_dir):
1134 """Reach into the hdctools repo and rsync only the servo directory."""
1135
1136 servo_dir = os.path.join(self.temp_hdctools_dir, 'servo')
1137 if not os.path.exists(servo_dir):
1138 return False
1139
1140 rv = self._rsync(servo_dir, os.path.join(install_dir, 'servo'))
1141 shutil.rmtree(self.temp_hdctools_dir)
1142 return rv
1143
1144
1145class ChromiteRepo(_ExternalGitRepo):
1146 """Clones or updates the chromite repo."""
1147
Alex Miller9fbe67f2013-09-06 15:04:48 -07001148 _GIT_URL = ('https://chromium.googlesource.com/chromiumos/chromite')
beepsd9153b52013-01-23 20:52:46 -08001149
1150 def build_and_install(self, install_dir):
1151 """
1152 Clone if the repo isn't initialized, pull clean bits if it is.
1153
1154 Unlike it's hdctools counterpart the chromite repo clones master
1155 directly into site-packages. It doesn't use an intermediate temp
Chris Sosa1bf41c42013-02-27 11:34:23 -08001156 directory because it doesn't need installation.
beepsd9153b52013-01-23 20:52:46 -08001157
1158 @param install_dir: destination directory for chromite installation.
1159 """
1160 local_chromite_dir = os.path.join(install_dir, 'chromite')
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001161 git_repo = revision_control.GitRepo(
1162 local_chromite_dir,
1163 self._GIT_URL,
1164 abs_work_tree=local_chromite_dir)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001165 git_repo.reinit_repo_at(self.PROD_BRANCH)
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001166
Chris Sosa1bf41c42013-02-27 11:34:23 -08001167
1168 if git_repo.get_latest_commit_hash():
1169 return True
1170 return False
1171
1172
1173class DevServerRepo(_ExternalGitRepo):
1174 """Clones or updates the chromite repo."""
1175
Alex Miller9fbe67f2013-09-06 15:04:48 -07001176 _GIT_URL = ('https://chromium.googlesource.com/'
Chris Sosa1bf41c42013-02-27 11:34:23 -08001177 'chromiumos/platform/dev-util')
1178
1179 def build_and_install(self, install_dir):
1180 """
1181 Clone if the repo isn't initialized, pull clean bits if it is.
1182
1183 Unlike it's hdctools counterpart the dev-util repo clones master
1184 directly into site-packages. It doesn't use an intermediate temp
1185 directory because it doesn't need installation.
1186
1187 @param install_dir: destination directory for chromite installation.
1188 """
1189 local_devserver_dir = os.path.join(install_dir, 'devserver')
1190 git_repo = revision_control.GitRepo(local_devserver_dir, self._GIT_URL,
1191 abs_work_tree=local_devserver_dir)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001192 git_repo.reinit_repo_at(self.PROD_BRANCH)
beepsd9153b52013-01-23 20:52:46 -08001193
1194 if git_repo.get_latest_commit_hash():
1195 return True
1196 return False
Scott James Remnantbb1a9672014-03-03 14:03:09 -08001197
1198
1199class BtsocketRepo(_ExternalGitRepo):
1200 """Clones or updates the btsocket repo."""
1201
1202 _GIT_URL = ('https://chromium.googlesource.com/'
1203 'chromiumos/platform/btsocket')
1204
1205 def fetch(self, unused_dest_dir):
1206 """
1207 Fetch repo to a temporary location.
1208
1209 We use an intermediate temp directory because we have to build an
1210 egg for installation. If we can't get at the top commit hash after
1211 fetching something is wrong. This can happen when we've cloned/pulled
1212 an empty repo. Not something we expect to do.
1213
1214 @parma unused_dest_dir: passed in because we inherit from
1215 ExternalPackage.
1216
1217 @return: True if repo sync was successful.
1218 """
1219 self.temp_btsocket_dir = autotemp.tempdir(unique_id='btsocket')
1220 try:
1221 git_repo = revision_control.GitRepo(
1222 self.temp_btsocket_dir.name,
1223 self._GIT_URL,
1224 None,
1225 abs_work_tree=self.temp_btsocket_dir.name)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001226 git_repo.reinit_repo_at(self.PROD_BRANCH)
Scott James Remnantbb1a9672014-03-03 14:03:09 -08001227
1228 if git_repo.get_latest_commit_hash():
1229 return True
1230 except:
1231 self.temp_btsocket_dir.clean()
1232 raise
1233
1234 self.temp_btsocket_dir.clean()
1235 return False
1236
1237
1238 def build_and_install(self, install_dir):
1239 """
1240 Install the btsocket module using setup.py
1241
1242 @param install_dir: Target installation directory.
1243
1244 @return: A boolean indicating success of failure.
1245 """
1246 work_dir = os.getcwd()
1247 try:
1248 os.chdir(self.temp_btsocket_dir.name)
1249 rv = self._build_and_install_current_dir_setup_py(install_dir)
1250 finally:
1251 os.chdir(work_dir)
1252 self.temp_btsocket_dir.clean()
J. Richard Barnette428b3442014-07-22 11:38:55 -07001253 return rv
Dan Shi44e47b82015-03-19 17:07:12 -07001254
1255
1256class NetifacesPackage(ExternalPackage):
1257 """netifaces package."""
1258 version = '0.10.4'
1259 url_filename = 'netifaces-%s.tar.gz' % version
1260 local_filename = url_filename
1261 urls = ('https://pypi.python.org/packages/source/n/netifaces/%s' %
1262 (url_filename),)
1263 hex_sum = 'c3fcd491a89c2994815053e853b005e7fc27c79a'
1264 _build_and_install = ExternalPackage._build_and_install_from_package
1265 _build_and_install_current_dir = (
1266 ExternalPackage._build_and_install_current_dir_setup_py)