blob: 2017eee6e2fb1cb59ecd27bb62eef0b8d2cf8d80 [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 """
Dan Shi16c0a502015-07-14 17:29:48 -070074 # Modules that are meant to be installed in system directory, rather than
75 # autotest/site-packages. These modules should be skipped if the module
76 # is already installed in system directory. This prevents an older version
77 # of the module from being installed in system directory.
78 SYSTEM_MODULES = ['setuptools']
79
jamesrenff6e5aa2010-02-12 00:46:40 +000080 subclasses = []
81 urls = ()
82 local_filename = None
83 hex_sum = None
84 module_name = None
85 version = None
86 os_requirements = None
87
88
89 class __metaclass__(type):
90 """Any time a subclass is defined, add it to our list."""
91 def __init__(mcs, name, bases, dict):
beepsd9153b52013-01-23 20:52:46 -080092 if name != 'ExternalPackage' and not name.startswith('_'):
jamesrenff6e5aa2010-02-12 00:46:40 +000093 mcs.subclasses.append(mcs)
94
95
96 def __init__(self):
97 self.verified_package = ''
98 if not self.module_name:
99 self.module_name = self.name.lower()
100 self.installed_version = ''
101
102
103 @property
104 def name(self):
105 """Return the class name with any trailing 'Package' stripped off."""
106 class_name = self.__class__.__name__
107 if class_name.endswith('Package'):
108 return class_name[:-len('Package')]
109 return class_name
110
111
Dan Shi7b6297b2015-06-23 13:54:09 -0700112 def is_needed(self, install_dir):
beepsd5335852013-04-09 09:58:52 -0700113 """
114 Check to see if we need to reinstall a package. This is contingent on:
115 1. Module name: If the name of the module is different from the package,
116 the class that installs it needs to specify a module_name string,
117 so we can try importing the module.
118
119 2. Installed version: If the module doesn't contain a __version__ the
120 class that installs it needs to override the
121 _get_installed_version_from_module method to return an appropriate
122 version string.
123
124 3. Version/Minimum version: The class that installs the package should
125 contain a version string, and an optional minimum version string.
Dan Shi57631dc2013-02-22 09:54:14 -0800126
Dan Shi7b6297b2015-06-23 13:54:09 -0700127 4. install_dir: If the module exists in a different directory, e.g.,
128 /usr/lib/python2.7/dist-packages/, the module will be forced to be
129 installed in install_dir.
130
131 @param install_dir: install directory.
beepsd5335852013-04-09 09:58:52 -0700132 @returns True if self.module_name needs to be built and installed.
Dan Shi57631dc2013-02-22 09:54:14 -0800133 """
jamesrenff6e5aa2010-02-12 00:46:40 +0000134 if not self.module_name or not self.version:
135 logging.warning('version and module_name required for '
136 'is_needed() check to work.')
137 return True
138 try:
139 module = __import__(self.module_name)
140 except ImportError, e:
jamesrenca2a9002010-04-20 22:33:22 +0000141 logging.info("%s isn't present. Will install.", self.module_name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000142 return True
Dan Shi16c0a502015-07-14 17:29:48 -0700143 if (not module.__file__.startswith(install_dir) and
144 not self.module_name in self.SYSTEM_MODULES):
Dan Shi7b6297b2015-06-23 13:54:09 -0700145 logging.info('Module %s is installed in %s, rather than %s. The '
146 'module will be forced to be installed in %s.',
147 self.module_name, module.__file__, install_dir,
148 install_dir)
149 return True
jamesrenff6e5aa2010-02-12 00:46:40 +0000150 self.installed_version = self._get_installed_version_from_module(module)
151 logging.info('imported %s version %s.', self.module_name,
152 self.installed_version)
Dale Curtis74a314b2011-06-23 14:55:46 -0700153 if hasattr(self, 'minimum_version'):
154 return self.minimum_version > self.installed_version
155 else:
156 return self.version > self.installed_version
jamesrenff6e5aa2010-02-12 00:46:40 +0000157
158
159 def _get_installed_version_from_module(self, module):
160 """Ask our module its version string and return it or '' if unknown."""
161 try:
162 return module.__version__
163 except AttributeError:
164 logging.error('could not get version from %s', module)
165 return ''
166
167
168 def _build_and_install(self, install_dir):
169 """Subclasses MUST provide their own implementation."""
170 raise NotImplementedError
171
172
173 def _build_and_install_current_dir(self, install_dir):
174 """
175 Subclasses that use _build_and_install_from_package() MUST provide
176 their own implementation of this method.
177 """
178 raise NotImplementedError
179
180
181 def build_and_install(self, install_dir):
182 """
183 Builds and installs the package. It must have been fetched already.
184
185 @param install_dir - The package installation directory. If it does
186 not exist it will be created.
187 """
188 if not self.verified_package:
189 raise Error('Must call fetch() first. - %s' % self.name)
190 self._check_os_requirements()
191 return self._build_and_install(install_dir)
192
193
194 def _check_os_requirements(self):
195 if not self.os_requirements:
196 return
197 failed = False
Michael Janssena7427612014-11-14 15:44:39 -0800198 for file_names, package_name in self.os_requirements.iteritems():
199 if not any(os.path.exists(file_name) for file_name in file_names):
jamesrenff6e5aa2010-02-12 00:46:40 +0000200 failed = True
Michael Janssena7427612014-11-14 15:44:39 -0800201 logging.error('Can\'t find %s, %s probably needs it.',
J. Richard Barnette6f7606b2015-06-11 17:26:23 -0700202 ' or '.join(file_names), self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000203 logging.error('Perhaps you need to install something similar '
204 'to the %s package for OS first.', package_name)
205 if failed:
206 raise Error('Missing OS requirements for %s. (see above)' %
207 self.name)
208
209
210 def _build_and_install_current_dir_setup_py(self, install_dir):
211 """For use as a _build_and_install_current_dir implementation."""
212 egg_path = self._build_egg_using_setup_py(setup_py='setup.py')
213 if not egg_path:
214 return False
215 return self._install_from_egg(install_dir, egg_path)
216
217
218 def _build_and_install_current_dir_setupegg_py(self, install_dir):
219 """For use as a _build_and_install_current_dir implementation."""
220 egg_path = self._build_egg_using_setup_py(setup_py='setupegg.py')
221 if not egg_path:
222 return False
223 return self._install_from_egg(install_dir, egg_path)
224
225
226 def _build_and_install_current_dir_noegg(self, install_dir):
227 if not self._build_using_setup_py():
228 return False
229 return self._install_using_setup_py_and_rsync(install_dir)
230
231
232 def _build_and_install_from_package(self, install_dir):
233 """
234 This method may be used as a _build_and_install() implementation
235 for subclasses if they implement _build_and_install_current_dir().
236
237 Extracts the .tar.gz file, chdirs into the extracted directory
238 (which is assumed to match the tar filename) and calls
239 _build_and_isntall_current_dir from there.
240
241 Afterwards the build (regardless of failure) extracted .tar.gz
242 directory is cleaned up.
243
244 @returns True on success, False otherwise.
245
246 @raises OSError If the expected extraction directory does not exist.
247 """
248 self._extract_compressed_package()
249 if self.verified_package.endswith('.tar.gz'):
250 extension = '.tar.gz'
251 elif self.verified_package.endswith('.tar.bz2'):
252 extension = '.tar.bz2'
253 elif self.verified_package.endswith('.zip'):
254 extension = '.zip'
255 else:
256 raise Error('Unexpected package file extension on %s' %
257 self.verified_package)
258 os.chdir(os.path.dirname(self.verified_package))
259 os.chdir(self.local_filename[:-len(extension)])
260 extracted_dir = os.getcwd()
261 try:
262 return self._build_and_install_current_dir(install_dir)
263 finally:
264 os.chdir(os.path.join(extracted_dir, '..'))
265 shutil.rmtree(extracted_dir)
266
267
268 def _extract_compressed_package(self):
269 """Extract the fetched compressed .tar or .zip within its directory."""
270 if not self.verified_package:
271 raise Error('Package must have been fetched first.')
272 os.chdir(os.path.dirname(self.verified_package))
273 if self.verified_package.endswith('gz'):
274 status = system("tar -xzf '%s'" % self.verified_package)
275 elif self.verified_package.endswith('bz2'):
276 status = system("tar -xjf '%s'" % self.verified_package)
277 elif self.verified_package.endswith('zip'):
278 status = system("unzip '%s'" % self.verified_package)
279 else:
280 raise Error('Unknown compression suffix on %s.' %
281 self.verified_package)
282 if status:
283 raise Error('tar failed with %s' % (status,))
284
285
286 def _build_using_setup_py(self, setup_py='setup.py'):
287 """
288 Assuming the cwd is the extracted python package, execute a simple
289 python setup.py build.
290
291 @param setup_py - The name of the setup.py file to execute.
292
293 @returns True on success, False otherwise.
294 """
295 if not os.path.exists(setup_py):
296 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
297 status = system("'%s' %s build" % (sys.executable, setup_py))
298 if status:
Dan Shi57631dc2013-02-22 09:54:14 -0800299 logging.error('%s build failed.', self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000300 return False
301 return True
302
303
304 def _build_egg_using_setup_py(self, setup_py='setup.py'):
305 """
306 Assuming the cwd is the extracted python package, execute a simple
307 python setup.py bdist_egg.
308
309 @param setup_py - The name of the setup.py file to execute.
310
311 @returns The relative path to the resulting egg file or '' on failure.
312 """
313 if not os.path.exists(setup_py):
314 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
315 egg_subdir = 'dist'
316 if os.path.isdir(egg_subdir):
317 shutil.rmtree(egg_subdir)
318 status = system("'%s' %s bdist_egg" % (sys.executable, setup_py))
319 if status:
320 logging.error('bdist_egg of setuptools failed.')
321 return ''
322 # I've never seen a bdist_egg lay multiple .egg files.
323 for filename in os.listdir(egg_subdir):
324 if filename.endswith('.egg'):
325 return os.path.join(egg_subdir, filename)
326
327
328 def _install_from_egg(self, install_dir, egg_path):
329 """
330 Install a module from an egg file by unzipping the necessary parts
331 into install_dir.
332
333 @param install_dir - The installation directory.
334 @param egg_path - The pathname of the egg file.
335 """
336 status = system("unzip -q -o -d '%s' '%s'" % (install_dir, egg_path))
337 if status:
338 logging.error('unzip of %s failed', egg_path)
339 return False
340 egg_info = os.path.join(install_dir, 'EGG-INFO')
341 if os.path.isdir(egg_info):
342 shutil.rmtree(egg_info)
343 return True
344
345
346 def _get_temp_dir(self):
347 return tempfile.mkdtemp(dir='/var/tmp')
348
349
350 def _site_packages_path(self, temp_dir):
351 # This makes assumptions about what python setup.py install
352 # does when given a prefix. Is this always correct?
353 python_xy = 'python%s' % sys.version[:3]
354 return os.path.join(temp_dir, 'lib', python_xy, 'site-packages')
355
356
beepsd9153b52013-01-23 20:52:46 -0800357 def _rsync (self, temp_site_dir, install_dir):
358 """Rsync contents. """
359 status = system("rsync -r '%s/' '%s/'" %
360 (os.path.normpath(temp_site_dir),
361 os.path.normpath(install_dir)))
362 if status:
363 logging.error('%s rsync to install_dir failed.', self.name)
364 return False
365 return True
366
367
jamesrenff6e5aa2010-02-12 00:46:40 +0000368 def _install_using_setup_py_and_rsync(self, install_dir,
369 setup_py='setup.py',
370 temp_dir=None):
371 """
372 Assuming the cwd is the extracted python package, execute a simple:
373
374 python setup.py install --prefix=BLA
375
376 BLA will be a temporary directory that everything installed will
377 be picked out of and rsynced to the appropriate place under
378 install_dir afterwards.
379
380 Afterwards, it deconstructs the extra lib/pythonX.Y/site-packages/
381 directory tree that setuptools created and moves all installed
382 site-packages directly up into install_dir itself.
383
384 @param install_dir the directory for the install to happen under.
385 @param setup_py - The name of the setup.py file to execute.
386
387 @returns True on success, False otherwise.
388 """
389 if not os.path.exists(setup_py):
390 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
391
392 if temp_dir is None:
393 temp_dir = self._get_temp_dir()
394
395 try:
396 status = system("'%s' %s install --no-compile --prefix='%s'"
397 % (sys.executable, setup_py, temp_dir))
398 if status:
Dan Shi57631dc2013-02-22 09:54:14 -0800399 logging.error('%s install failed.', self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000400 return False
401
402 if os.path.isdir(os.path.join(temp_dir, 'lib')):
403 # NOTE: This ignores anything outside of the lib/ dir that
404 # was installed.
405 temp_site_dir = self._site_packages_path(temp_dir)
406 else:
407 temp_site_dir = temp_dir
408
beepsd9153b52013-01-23 20:52:46 -0800409 return self._rsync(temp_site_dir, install_dir)
jamesrenff6e5aa2010-02-12 00:46:40 +0000410 finally:
411 shutil.rmtree(temp_dir)
412
413
414
415 def _build_using_make(self, install_dir):
416 """Build the current package using configure/make.
417
418 @returns True on success, False otherwise.
419 """
420 install_prefix = os.path.join(install_dir, 'usr', 'local')
421 status = system('./configure --prefix=%s' % install_prefix)
422 if status:
423 logging.error('./configure failed for %s', self.name)
424 return False
425 status = system('make')
426 if status:
427 logging.error('make failed for %s', self.name)
428 return False
429 status = system('make check')
430 if status:
431 logging.error('make check failed for %s', self.name)
432 return False
433 return True
434
435
436 def _install_using_make(self):
437 """Install the current package using make install.
438
439 Assumes the install path was set up while running ./configure (in
440 _build_using_make()).
441
442 @returns True on success, False otherwise.
443 """
444 status = system('make install')
445 return status == 0
446
447
448 def fetch(self, dest_dir):
449 """
450 Fetch the package from one its URLs and save it in dest_dir.
451
452 If the the package already exists in dest_dir and the checksum
453 matches this code will not fetch it again.
454
455 Sets the 'verified_package' attribute with the destination pathname.
456
457 @param dest_dir - The destination directory to save the local file.
458 If it does not exist it will be created.
459
460 @returns A boolean indicating if we the package is now in dest_dir.
461 @raises FetchError - When something unexpected happens.
462 """
463 if not os.path.exists(dest_dir):
464 os.makedirs(dest_dir)
465 local_path = os.path.join(dest_dir, self.local_filename)
466
467 # If the package exists, verify its checksum and be happy if it is good.
468 if os.path.exists(local_path):
469 actual_hex_sum = _checksum_file(local_path)
470 if self.hex_sum == actual_hex_sum:
471 logging.info('Good checksum for existing %s package.',
472 self.name)
473 self.verified_package = local_path
474 return True
475 logging.warning('Bad checksum for existing %s package. '
476 'Re-downloading', self.name)
477 os.rename(local_path, local_path + '.wrong-checksum')
478
479 # Download the package from one of its urls, rejecting any if the
480 # checksum does not match.
481 for url in self.urls:
482 logging.info('Fetching %s', url)
483 try:
484 url_file = urllib2.urlopen(url)
485 except (urllib2.URLError, EnvironmentError):
486 logging.warning('Could not fetch %s package from %s.',
487 self.name, url)
488 continue
Dan Shi57631dc2013-02-22 09:54:14 -0800489
jamesrenff6e5aa2010-02-12 00:46:40 +0000490 data_length = int(url_file.info().get('Content-Length',
491 _MAX_PACKAGE_SIZE))
492 if data_length <= 0 or data_length > _MAX_PACKAGE_SIZE:
493 raise FetchError('%s from %s fails Content-Length %d '
494 'sanity check.' % (self.name, url,
495 data_length))
496 checksum = utils.hash('sha1')
497 total_read = 0
498 output = open(local_path, 'wb')
499 try:
500 while total_read < data_length:
501 data = url_file.read(_READ_SIZE)
502 if not data:
503 break
504 output.write(data)
505 checksum.update(data)
506 total_read += len(data)
507 finally:
508 output.close()
509 if self.hex_sum != checksum.hexdigest():
510 logging.warning('Bad checksum for %s fetched from %s.',
511 self.name, url)
512 logging.warning('Got %s', checksum.hexdigest())
513 logging.warning('Expected %s', self.hex_sum)
514 os.unlink(local_path)
515 continue
516 logging.info('Good checksum.')
517 self.verified_package = local_path
518 return True
519 else:
520 return False
521
522
523# NOTE: This class definition must come -before- all other ExternalPackage
524# classes that need to use this version of setuptools so that is is inserted
525# into the ExternalPackage.subclasses list before them.
526class SetuptoolsPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800527 """setuptools package"""
jamesrenff6e5aa2010-02-12 00:46:40 +0000528 # For all known setuptools releases a string compare works for the
529 # version string. Hopefully they never release a 0.10. (Their own
530 # version comparison code would break if they did.)
Dan Shi16c0a502015-07-14 17:29:48 -0700531 # Any system with setuptools > 18.0.1 is fine. If none installed, then
Dale Curtis74a314b2011-06-23 14:55:46 -0700532 # try to install the latest found on the upstream.
Dan Shi16c0a502015-07-14 17:29:48 -0700533 minimum_version = '18.0.1'
534 version = '18.0.1'
jamesrenff6e5aa2010-02-12 00:46:40 +0000535 urls = ('http://pypi.python.org/packages/source/s/setuptools/'
536 'setuptools-%s.tar.gz' % (version,),)
537 local_filename = 'setuptools-%s.tar.gz' % version
Dan Shi16c0a502015-07-14 17:29:48 -0700538 hex_sum = 'ebc4fe81b7f6d61d923d9519f589903824044f52'
jamesrenff6e5aa2010-02-12 00:46:40 +0000539
540 SUDO_SLEEP_DELAY = 15
541
542
543 def _build_and_install(self, install_dir):
544 """Install setuptools on the system."""
545 logging.info('NOTE: setuptools install does not use install_dir.')
546 return self._build_and_install_from_package(install_dir)
547
548
549 def _build_and_install_current_dir(self, install_dir):
550 egg_path = self._build_egg_using_setup_py()
551 if not egg_path:
552 return False
553
554 print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n'
555 print 'About to run sudo to install setuptools', self.version
556 print 'on your system for use by', sys.executable, '\n'
557 print '!! ^C within', self.SUDO_SLEEP_DELAY, 'seconds to abort.\n'
558 time.sleep(self.SUDO_SLEEP_DELAY)
559
560 # Copy the egg to the local filesystem /var/tmp so that root can
561 # access it properly (avoid NFS squashroot issues).
562 temp_dir = self._get_temp_dir()
563 try:
564 shutil.copy(egg_path, temp_dir)
565 egg_name = os.path.split(egg_path)[1]
566 temp_egg = os.path.join(temp_dir, egg_name)
567 p = subprocess.Popen(['sudo', '/bin/sh', temp_egg],
568 stdout=subprocess.PIPE)
569 regex = re.compile('Copying (.*?) to (.*?)\n')
570 match = regex.search(p.communicate()[0])
571 status = p.wait()
572
573 if match:
574 compiled = os.path.join(match.group(2), match.group(1))
575 os.system("sudo chmod a+r '%s'" % compiled)
576 finally:
577 shutil.rmtree(temp_dir)
578
579 if status:
580 logging.error('install of setuptools from egg failed.')
581 return False
582 return True
583
584
585class MySQLdbPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800586 """mysql package, used in scheduler."""
jamesrenff6e5aa2010-02-12 00:46:40 +0000587 module_name = 'MySQLdb'
Alex Miller47d61282013-04-17 13:53:58 -0700588 version = '1.2.3'
jamesrenff6e5aa2010-02-12 00:46:40 +0000589 urls = ('http://downloads.sourceforge.net/project/mysql-python/'
590 'mysql-python/%(version)s/MySQL-python-%(version)s.tar.gz'
591 % dict(version=version),)
592 local_filename = 'MySQL-python-%s.tar.gz' % version
Alex Miller47d61282013-04-17 13:53:58 -0700593 hex_sum = '3511bb8c57c6016eeafa531d5c3ea4b548915e3c'
jamesrenff6e5aa2010-02-12 00:46:40 +0000594
595 _build_and_install_current_dir = (
596 ExternalPackage._build_and_install_current_dir_setup_py)
597
598
599 def _build_and_install(self, install_dir):
600 if not os.path.exists('/usr/bin/mysql_config'):
Dan Shi16c0a502015-07-14 17:29:48 -0700601 error_msg = ('You need to install /usr/bin/mysql_config.\n'
602 'On Ubuntu or Debian based systems use this: '
603 'sudo apt-get install libmysqlclient15-dev')
604 logging.error(error_msg)
605 return False, error_msg
jamesrenff6e5aa2010-02-12 00:46:40 +0000606 return self._build_and_install_from_package(install_dir)
607
608
609class DjangoPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800610 """django package."""
Alex Miller47d61282013-04-17 13:53:58 -0700611 version = '1.5.1'
jamesrenff6e5aa2010-02-12 00:46:40 +0000612 local_filename = 'Django-%s.tar.gz' % version
613 urls = ('http://www.djangoproject.com/download/%s/tarball/' % version,)
Alex Miller47d61282013-04-17 13:53:58 -0700614 hex_sum = '0ab97b90c4c79636e56337f426f1e875faccbba1'
jamesrenff6e5aa2010-02-12 00:46:40 +0000615
616 _build_and_install = ExternalPackage._build_and_install_from_package
617 _build_and_install_current_dir = (
618 ExternalPackage._build_and_install_current_dir_noegg)
619
620
621 def _get_installed_version_from_module(self, module):
622 try:
623 return module.get_version().split()[0]
624 except AttributeError:
625 return '0.9.6'
626
627
628
629class NumpyPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800630 """numpy package, required by matploglib."""
Scott Zawalskic2c9a012013-03-06 09:13:26 -0500631 version = '1.7.0'
jamesrenff6e5aa2010-02-12 00:46:40 +0000632 local_filename = 'numpy-%s.tar.gz' % version
633 urls = ('http://downloads.sourceforge.net/project/numpy/NumPy/%(version)s/'
634 'numpy-%(version)s.tar.gz' % dict(version=version),)
Scott Zawalskic2c9a012013-03-06 09:13:26 -0500635 hex_sum = 'ba328985f20390b0f969a5be2a6e1141d5752cf9'
jamesrenff6e5aa2010-02-12 00:46:40 +0000636
637 _build_and_install = ExternalPackage._build_and_install_from_package
638 _build_and_install_current_dir = (
639 ExternalPackage._build_and_install_current_dir_setupegg_py)
640
641
jamesrenff6e5aa2010-02-12 00:46:40 +0000642class MatplotlibPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800643 """
644 matplotlib package
645
646 This requires numpy so it must be declared after numpy to guarantee that
647 it is already installed.
648 """
jamesrenff6e5aa2010-02-12 00:46:40 +0000649 version = '0.98.5.3'
650 short_version = '0.98.5'
651 local_filename = 'matplotlib-%s.tar.gz' % version
652 urls = ('http://downloads.sourceforge.net/project/matplotlib/matplotlib/'
653 'matplotlib-%s/matplotlib-%s.tar.gz' % (short_version, version),)
654 hex_sum = '2f6c894cf407192b3b60351bcc6468c0385d47b6'
Michael Janssena7427612014-11-14 15:44:39 -0800655 os_requirements = {('/usr/include/freetype2/ft2build.h',
656 '/usr/include/ft2build.h'): 'libfreetype6-dev',
657 ('/usr/include/png.h'): 'libpng12-dev'}
jamesrenff6e5aa2010-02-12 00:46:40 +0000658
659 _build_and_install = ExternalPackage._build_and_install_from_package
660 _build_and_install_current_dir = (
661 ExternalPackage._build_and_install_current_dir_setupegg_py)
662
663
664class AtForkPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800665 """atfork package"""
jamesrenff6e5aa2010-02-12 00:46:40 +0000666 version = '0.1.2'
667 local_filename = 'atfork-%s.zip' % version
668 urls = ('http://python-atfork.googlecode.com/files/' + local_filename,)
669 hex_sum = '5baa64c73e966b57fa797040585c760c502dc70b'
670
671 _build_and_install = ExternalPackage._build_and_install_from_package
672 _build_and_install_current_dir = (
673 ExternalPackage._build_and_install_current_dir_noegg)
674
675
676class ParamikoPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800677 """paramiko package"""
jamesrenff6e5aa2010-02-12 00:46:40 +0000678 version = '1.7.5'
Shuqian Zhaoa2642062016-01-27 20:00:24 -0800679 local_filename = 'paramiko-%s.zip' % version
680 urls = ('https://pypi.python.org/packages/source/p/paramiko/' + local_filename,)
681 hex_sum = 'd23e437c0d8bd6aeb181d9990a9d670fb30d0c72'
jamesrenff6e5aa2010-02-12 00:46:40 +0000682
683
684 _build_and_install = ExternalPackage._build_and_install_from_package
685
686
687 def _check_for_pycrypto(self):
688 # NOTE(gps): Linux distros have better python-crypto packages than we
689 # can easily get today via a wget due to the library's age and staleness
690 # yet many security and behavior bugs are fixed by patches that distros
691 # already apply. PyCrypto has a new active maintainer in 2009. Once a
692 # new release is made (http://pycrypto.org/) we should add an installer.
693 try:
694 import Crypto
695 except ImportError:
696 logging.error('Please run "sudo apt-get install python-crypto" '
697 'or your Linux distro\'s equivalent.')
698 return False
699 return True
700
701
702 def _build_and_install_current_dir(self, install_dir):
703 if not self._check_for_pycrypto():
704 return False
705 # paramiko 1.7.4 doesn't require building, it is just a module directory
706 # that we can rsync into place directly.
707 if not os.path.isdir('paramiko'):
708 raise Error('no paramiko directory in %s.' % os.getcwd())
709 status = system("rsync -r 'paramiko' '%s/'" % install_dir)
710 if status:
Dan Shi57631dc2013-02-22 09:54:14 -0800711 logging.error('%s rsync to install_dir failed.', self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000712 return False
713 return True
714
715
Chris Masonebafbbb02012-05-16 13:41:36 -0700716class RequestsPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800717 """requests package"""
Chris Masonebafbbb02012-05-16 13:41:36 -0700718 version = '0.11.2'
719 local_filename = 'requests-%s.tar.gz' % version
720 urls = ('http://pypi.python.org/packages/source/r/requests/' +
721 local_filename,)
722 hex_sum = '00a49e8bd6dd8955acf6f6269d1b85f50c70b712'
723
724 _build_and_install = ExternalPackage._build_and_install_from_package
725 _build_and_install_current_dir = (
726 ExternalPackage._build_and_install_current_dir_setup_py)
727
728
beeps32a63082013-08-22 14:02:29 -0700729class JsonRPCLib(ExternalPackage):
730 """jsonrpclib package"""
731 version = '0.1.3'
732 module_name = 'jsonrpclib'
733 local_filename = '%s-%s.tar.gz' % (module_name, version)
734 urls = ('http://pypi.python.org/packages/source/j/%s/%s' %
735 (module_name, local_filename), )
736 hex_sum = '431714ed19ab677f641ce5d678a6a95016f5c452'
737
738 def _get_installed_version_from_module(self, module):
739 # jsonrpclib doesn't contain a proper version
740 return self.version
741
742 _build_and_install = ExternalPackage._build_and_install_from_package
743 _build_and_install_current_dir = (
744 ExternalPackage._build_and_install_current_dir_noegg)
745
746
jamesrenff6e5aa2010-02-12 00:46:40 +0000747class Httplib2Package(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800748 """httplib2 package"""
jamesrenff6e5aa2010-02-12 00:46:40 +0000749 version = '0.6.0'
750 local_filename = 'httplib2-%s.tar.gz' % version
751 urls = ('http://httplib2.googlecode.com/files/' + local_filename,)
752 hex_sum = '995344b2704826cc0d61a266e995b328d92445a5'
753
754 def _get_installed_version_from_module(self, module):
755 # httplib2 doesn't contain a proper version
756 return self.version
757
758 _build_and_install = ExternalPackage._build_and_install_from_package
759 _build_and_install_current_dir = (
760 ExternalPackage._build_and_install_current_dir_noegg)
761
762
763class GwtPackage(ExternalPackage):
764 """Fetch and extract a local copy of GWT used to build the frontend."""
765
Dale Curtis74a314b2011-06-23 14:55:46 -0700766 version = '2.3.0'
jamesren012d0322010-04-30 20:21:29 +0000767 local_filename = 'gwt-%s.zip' % version
jamesrenff6e5aa2010-02-12 00:46:40 +0000768 urls = ('http://google-web-toolkit.googlecode.com/files/' + local_filename,)
Dale Curtis74a314b2011-06-23 14:55:46 -0700769 hex_sum = 'd51fce9166e6b31349659ffca89baf93e39bc84b'
jamesrenff6e5aa2010-02-12 00:46:40 +0000770 name = 'gwt'
771 about_filename = 'about.txt'
772 module_name = None # Not a Python module.
773
774
775 def is_needed(self, install_dir):
776 gwt_dir = os.path.join(install_dir, self.name)
777 about_file = os.path.join(install_dir, self.name, self.about_filename)
778
779 if not os.path.exists(gwt_dir) or not os.path.exists(about_file):
780 logging.info('gwt not installed for autotest')
781 return True
782
783 f = open(about_file, 'r')
784 version_line = f.readline()
785 f.close()
786
787 match = re.match(r'Google Web Toolkit (.*)', version_line)
788 if not match:
789 logging.info('did not find gwt version')
790 return True
791
792 logging.info('found gwt version %s', match.group(1))
793 return match.group(1) != self.version
794
795
jamesrenca2a9002010-04-20 22:33:22 +0000796 def _build_and_install(self, install_dir):
jamesrenff6e5aa2010-02-12 00:46:40 +0000797 os.chdir(install_dir)
798 self._extract_compressed_package()
jamesren012d0322010-04-30 20:21:29 +0000799 extracted_dir = self.local_filename[:-len('.zip')]
jamesrenff6e5aa2010-02-12 00:46:40 +0000800 target_dir = os.path.join(install_dir, self.name)
801 if os.path.exists(target_dir):
802 shutil.rmtree(target_dir)
803 os.rename(extracted_dir, target_dir)
804 return True
805
806
Mike Trutyddd44b22011-04-14 15:38:56 -0700807class GVizAPIPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800808 """gviz package"""
Alex Miller0e1217c2013-02-22 10:11:08 -0800809 module_name = 'gviz_api'
Mike Trutyddd44b22011-04-14 15:38:56 -0700810 version = '1.7.0'
811 url_filename = 'gviz_api_py-%s.tar.gz' % version
812 local_filename = 'google-visualization-python.tar.gz'
813 urls = ('http://google-visualization-python.googlecode.com/files/%s' % (
814 url_filename),)
815 hex_sum = 'cd9a0fb4ca5c4f86c0d85756f501fd54ccf492d2'
816
817 _build_and_install = ExternalPackage._build_and_install_from_package
818 _build_and_install_current_dir = (
819 ExternalPackage._build_and_install_current_dir_noegg)
beepsd9153b52013-01-23 20:52:46 -0800820
Alex Miller0e1217c2013-02-22 10:11:08 -0800821 def _get_installed_version_from_module(self, module):
822 # gviz doesn't contain a proper version
823 return self.version
824
beepsd9153b52013-01-23 20:52:46 -0800825
Scott Zawalski2a6acf82013-02-11 16:18:27 -0500826class StatsdPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800827 """python-statsd package"""
Fang Dengc0594a62015-07-14 15:14:48 -0700828 version = '1.7.2'
Scott Zawalski2a6acf82013-02-11 16:18:27 -0500829 url_filename = 'python-statsd-%s.tar.gz' % version
830 local_filename = url_filename
831 urls = ('http://pypi.python.org/packages/source/p/python-statsd/%s' % (
832 url_filename),)
Fang Dengc0594a62015-07-14 15:14:48 -0700833 hex_sum = '2cc186ebdb723e2420b432ab71639786d877694b'
Scott Zawalski2a6acf82013-02-11 16:18:27 -0500834
835 _build_and_install = ExternalPackage._build_and_install_from_package
836 _build_and_install_current_dir = (
837 ExternalPackage._build_and_install_current_dir_setup_py)
838
839
beeps93bef482013-02-04 16:24:22 -0800840class GdataPackage(ExternalPackage):
841 """
842 Pulls the GData library, giving us an API to query tracker.
843 """
844
845 version = '2.0.14'
846 url_filename = 'gdata-%s.tar.gz' % version
847 local_filename = url_filename
848 urls = ('http://gdata-python-client.googlecode.com/files/%s' % (
849 url_filename),)
850 hex_sum = '5eed0e01ab931e3f706ec544fc8f06ecac384e91'
851
852 _build_and_install = ExternalPackage._build_and_install_from_package
853 _build_and_install_current_dir = (
854 ExternalPackage._build_and_install_current_dir_noegg)
855
Alex Miller0e1217c2013-02-22 10:11:08 -0800856 def _get_installed_version_from_module(self, module):
857 # gdata doesn't contain a proper version
858 return self.version
859
beeps93bef482013-02-04 16:24:22 -0800860
beepse0db09d2013-02-24 17:13:21 -0800861class GoogleAPIClientPackage(ExternalPackage):
862 """
863 Pulls the Python Google API client library.
864 """
865 version = '1.1'
beepsd5335852013-04-09 09:58:52 -0700866 module_name = 'apiclient'
beepse0db09d2013-02-24 17:13:21 -0800867 url_filename = 'google-api-python-client-%s.tar.gz' % version
868 local_filename = url_filename
869 urls = ('https://google-api-python-client.googlecode.com/files/%s' % (
870 url_filename),)
871 hex_sum = '2294949683e367b3d4ecaeb77502509c5af21e60'
872
873 _build_and_install = ExternalPackage._build_and_install_from_package
874 _build_and_install_current_dir = (
875 ExternalPackage._build_and_install_current_dir_setup_py)
876
877
beepsd5335852013-04-09 09:58:52 -0700878class GFlagsPackage(ExternalPackage):
879 """
880 Gets the Python GFlags client library.
881 """
882 # gflags doesn't contain a proper version
883 version = '2.0'
884 url_filename = 'python-gflags-%s.tar.gz' % version
885 local_filename = url_filename
886 urls = ('https://python-gflags.googlecode.com/files/%s' % (
887 url_filename),)
888 hex_sum = 'db309e6964b102ff36de319ce551db512a78281e'
889
890 _build_and_install = ExternalPackage._build_and_install_from_package
891 _build_and_install_current_dir = (
892 ExternalPackage._build_and_install_current_dir_setup_py)
893
894
895 def _get_installed_version_from_module(self, module):
896 return self.version
897
898
Alex Millere7b6e8b2013-02-13 17:34:04 -0800899class DnsPythonPackage(ExternalPackage):
900 """
901 dns module
902
903 Used in unittests.
904 """
905 module_name = 'dns'
906 version = '1.3.5'
907 url_filename = 'dnspython-%s.tar.gz' % version
908 local_filename = url_filename
909 urls = ('http://www.dnspython.org/kits/%s/%s' % (
Dan Shi57631dc2013-02-22 09:54:14 -0800910 version, url_filename),)
911
Alex Millere7b6e8b2013-02-13 17:34:04 -0800912 hex_sum = '06314dad339549613435470c6add992910e26e5d'
913
914 _build_and_install = ExternalPackage._build_and_install_from_package
915 _build_and_install_current_dir = (
916 ExternalPackage._build_and_install_current_dir_noegg)
917
918 def _get_installed_version_from_module(self, module):
919 """Ask our module its version string and return it or '' if unknown."""
920 try:
921 __import__(self.module_name + '.version')
922 return module.version.version
923 except AttributeError:
924 logging.error('could not get version from %s', module)
925 return ''
926
927
928class PyudevPackage(ExternalPackage):
929 """
930 pyudev module
931
932 Used in unittests.
933 """
934 version = '0.16.1'
935 url_filename = 'pyudev-%s.tar.gz' % version
936 local_filename = url_filename
937 urls = ('http://pypi.python.org/packages/source/p/pyudev/%s' % (
938 url_filename),)
939 hex_sum = 'b36bc5c553ce9b56d32a5e45063a2c88156771c0'
940
941 _build_and_install = ExternalPackage._build_and_install_from_package
942 _build_and_install_current_dir = (
943 ExternalPackage._build_and_install_current_dir_setup_py)
944
945
946class PyMoxPackage(ExternalPackage):
947 """
948 mox module
949
950 Used in unittests.
951 """
952 module_name = 'mox'
953 version = '0.5.3'
954 url_filename = 'mox-%s.tar.gz' % version
955 local_filename = url_filename
956 urls = ('http://pypi.python.org/packages/source/m/mox/%s' % (
957 url_filename),)
958 hex_sum = '1c502d2c0a8aefbba2c7f385a83d33e7d822452a'
959
960 _build_and_install = ExternalPackage._build_and_install_from_package
961 _build_and_install_current_dir = (
962 ExternalPackage._build_and_install_current_dir_noegg)
963
964 def _get_installed_version_from_module(self, module):
965 # mox doesn't contain a proper version
966 return self.version
967
968
Jason Abele2371d1a2013-11-22 12:18:18 -0800969class PySeleniumPackage(ExternalPackage):
970 """
971 selenium module
972
973 Used in wifi_interop suite.
974 """
975 module_name = 'selenium'
976 version = '2.37.2'
977 url_filename = 'selenium-%s.tar.gz' % version
978 local_filename = url_filename
979 urls = ('https://pypi.python.org/packages/source/s/selenium/%s' % (
980 url_filename),)
981 hex_sum = '66946d5349e36d946daaad625c83c30c11609e36'
982
983 _build_and_install = ExternalPackage._build_and_install_from_package
984 _build_and_install_current_dir = (
985 ExternalPackage._build_and_install_current_dir_setup_py)
986
987
Simran Basid6b83772014-01-06 16:31:30 -0800988class FaultHandlerPackage(ExternalPackage):
989 """
990 faulthandler module
991 """
992 module_name = 'faulthandler'
993 version = '2.3'
994 url_filename = '%s-%s.tar.gz' % (module_name, version)
995 local_filename = url_filename
996 urls = ('http://pypi.python.org/packages/source/f/faulthandler/%s' %
997 (url_filename),)
998 hex_sum = 'efb30c068414fba9df892e48fcf86170cbf53589'
999
1000 _build_and_install = ExternalPackage._build_and_install_from_package
1001 _build_and_install_current_dir = (
1002 ExternalPackage._build_and_install_current_dir_noegg)
1003
1004
Alex Millera87e5482014-06-09 16:04:49 -07001005class PsutilPackage(ExternalPackage):
1006 """
1007 psutil module
1008 """
1009 module_name = 'psutil'
1010 version = '2.1.1'
1011 url_filename = '%s-%s.tar.gz' % (module_name, version)
1012 local_filename = url_filename
1013 urls = ('http://pypi.python.org/packages/source/p/psutil/%s' %
1014 (url_filename),)
1015 hex_sum = '0c20a20ed316e69f2b0881530439213988229916'
1016
1017 _build_and_install = ExternalPackage._build_and_install_from_package
1018 _build_and_install_current_dir = (
1019 ExternalPackage._build_and_install_current_dir_setup_py)
1020
1021
Michael Liangd2d294c2014-06-24 15:24:49 -07001022class ElasticSearchPackage(ExternalPackage):
1023 """elasticsearch-py package."""
Dan Shicae83c72015-07-22 17:58:21 -07001024 version = '1.6.0'
Michael Liangd2d294c2014-06-24 15:24:49 -07001025 url_filename = 'elasticsearch-%s.tar.gz' % version
1026 local_filename = url_filename
Michael Liang5934b712014-08-12 15:45:14 -07001027 urls = ('https://pypi.python.org/packages/source/e/elasticsearch/%s' %
1028 (url_filename),)
Dan Shicae83c72015-07-22 17:58:21 -07001029 hex_sum = '3e676c96f47935b1f52df82df3969564bd356b1c'
Michael Liangd2d294c2014-06-24 15:24:49 -07001030 _build_and_install = ExternalPackage._build_and_install_from_package
1031 _build_and_install_current_dir = (
1032 ExternalPackage._build_and_install_current_dir_setup_py)
1033
1034
Michael Liang5934b712014-08-12 15:45:14 -07001035class Urllib3Package(ExternalPackage):
1036 """elasticsearch-py package."""
1037 version = '1.9'
1038 url_filename = 'urllib3-%s.tar.gz' % version
1039 local_filename = url_filename
1040 urls = ('https://pypi.python.org/packages/source/u/urllib3/%s' %
1041 (url_filename),)
1042 hex_sum = '9522197efb2a2b49ce804de3a515f06d97b6602f'
1043 _build_and_install = ExternalPackage._build_and_install_from_package
1044 _build_and_install_current_dir = (
1045 ExternalPackage._build_and_install_current_dir_setup_py)
1046
1047
J. Richard Barnette428b3442014-07-22 11:38:55 -07001048class ImagingLibraryPackage(ExternalPackage):
1049 """Python Imaging Library (PIL)."""
1050 version = '1.1.7'
1051 url_filename = 'Imaging-%s.tar.gz' % version
1052 local_filename = url_filename
1053 urls = ('http://effbot.org/downloads/%s' % url_filename,)
1054 hex_sum = '76c37504251171fda8da8e63ecb8bc42a69a5c81'
Christopher Wileyf7a46372015-12-16 10:00:30 -08001055
1056 def _build_and_install(self, install_dir):
1057 # The path of zlib library might be different from what PIL setup.py is
1058 # expected. Following change does the best attempt to link the library
1059 # to a path PIL setup.py will try.
1060 libz_possible_path = '/usr/lib/x86_64-linux-gnu/libz.so'
1061 libz_expected_path = '/usr/lib/libz.so'
1062 if (os.path.exists(libz_possible_path) and
1063 not os.path.exists(libz_expected_path)):
1064 utils.run('sudo ln -s %s %s' %
1065 (libz_possible_path, libz_expected_path))
1066 return self._build_and_install_from_package(install_dir)
1067
J. Richard Barnette428b3442014-07-22 11:38:55 -07001068 _build_and_install_current_dir = (
1069 ExternalPackage._build_and_install_current_dir_noegg)
1070
1071
beepsd9153b52013-01-23 20:52:46 -08001072class _ExternalGitRepo(ExternalPackage):
1073 """
1074 Parent class for any package which needs to pull a git repo.
1075
1076 This class inherits from ExternalPackage only so we can sync git
1077 repos through the build_externals script. We do not reuse any of
1078 ExternalPackage's other methods. Any package that needs a git repo
1079 should subclass this and override build_and_install or fetch as
1080 they see appropriate.
1081 """
1082
Michael Janssena7427612014-11-14 15:44:39 -08001083 os_requirements = {('/usr/bin/git') : 'git-core'}
beepsd9153b52013-01-23 20:52:46 -08001084
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001085 # All the chromiumos projects used on the lab servers should have a 'prod'
1086 # branch used to track the software version deployed in prod.
1087 PROD_BRANCH = 'prod'
1088
beepsd9153b52013-01-23 20:52:46 -08001089 def is_needed(self, unused_install_dir):
1090 """Tell build_externals that we need to fetch."""
1091 # TODO(beeps): check if we're already upto date.
1092 return True
1093
1094
1095 def build_and_install(self, unused_install_dir):
1096 """
1097 Fall through method to install a package.
1098
1099 Overwritten in base classes to pull a git repo.
1100 """
1101 raise NotImplementedError
1102
1103
1104 def fetch(self, unused_dest_dir):
1105 """Fallthrough method to fetch a package."""
1106 return True
1107
1108
1109class HdctoolsRepo(_ExternalGitRepo):
1110 """Clones or updates the hdctools repo."""
1111
Alex Miller0e1217c2013-02-22 10:11:08 -08001112 module_name = 'servo'
beepsd9153b52013-01-23 20:52:46 -08001113 temp_hdctools_dir = tempfile.mktemp(suffix='hdctools')
Alex Miller9fbe67f2013-09-06 15:04:48 -07001114 _GIT_URL = ('https://chromium.googlesource.com/'
beepsd9153b52013-01-23 20:52:46 -08001115 'chromiumos/third_party/hdctools')
1116
1117 def fetch(self, unused_dest_dir):
1118 """
1119 Fetch repo to a temporary location.
1120
1121 We use an intermediate temp directory to stage our
1122 installation because we only care about the servo package.
1123 If we can't get at the top commit hash after fetching
1124 something is wrong. This can happen when we've cloned/pulled
1125 an empty repo. Not something we expect to do.
1126
1127 @parma unused_dest_dir: passed in because we inherit from
1128 ExternalPackage.
1129
1130 @return: True if repo sync was successful.
1131 """
1132 git_repo = revision_control.GitRepo(
1133 self.temp_hdctools_dir,
1134 self._GIT_URL,
1135 None,
beepsaae3f1c2013-03-19 15:49:14 -07001136 abs_work_tree=self.temp_hdctools_dir)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001137 git_repo.reinit_repo_at(self.PROD_BRANCH)
beepsd9153b52013-01-23 20:52:46 -08001138
1139 if git_repo.get_latest_commit_hash():
1140 return True
1141 return False
1142
1143
1144 def build_and_install(self, install_dir):
1145 """Reach into the hdctools repo and rsync only the servo directory."""
1146
1147 servo_dir = os.path.join(self.temp_hdctools_dir, 'servo')
1148 if not os.path.exists(servo_dir):
1149 return False
1150
1151 rv = self._rsync(servo_dir, os.path.join(install_dir, 'servo'))
1152 shutil.rmtree(self.temp_hdctools_dir)
1153 return rv
1154
1155
1156class ChromiteRepo(_ExternalGitRepo):
1157 """Clones or updates the chromite repo."""
1158
Alex Miller9fbe67f2013-09-06 15:04:48 -07001159 _GIT_URL = ('https://chromium.googlesource.com/chromiumos/chromite')
beepsd9153b52013-01-23 20:52:46 -08001160
1161 def build_and_install(self, install_dir):
1162 """
1163 Clone if the repo isn't initialized, pull clean bits if it is.
1164
1165 Unlike it's hdctools counterpart the chromite repo clones master
1166 directly into site-packages. It doesn't use an intermediate temp
Chris Sosa1bf41c42013-02-27 11:34:23 -08001167 directory because it doesn't need installation.
beepsd9153b52013-01-23 20:52:46 -08001168
1169 @param install_dir: destination directory for chromite installation.
1170 """
1171 local_chromite_dir = os.path.join(install_dir, 'chromite')
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001172 git_repo = revision_control.GitRepo(
1173 local_chromite_dir,
1174 self._GIT_URL,
1175 abs_work_tree=local_chromite_dir)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001176 git_repo.reinit_repo_at(self.PROD_BRANCH)
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001177
Chris Sosa1bf41c42013-02-27 11:34:23 -08001178
1179 if git_repo.get_latest_commit_hash():
1180 return True
1181 return False
1182
1183
1184class DevServerRepo(_ExternalGitRepo):
1185 """Clones or updates the chromite repo."""
1186
Alex Miller9fbe67f2013-09-06 15:04:48 -07001187 _GIT_URL = ('https://chromium.googlesource.com/'
Chris Sosa1bf41c42013-02-27 11:34:23 -08001188 'chromiumos/platform/dev-util')
1189
1190 def build_and_install(self, install_dir):
1191 """
1192 Clone if the repo isn't initialized, pull clean bits if it is.
1193
1194 Unlike it's hdctools counterpart the dev-util repo clones master
1195 directly into site-packages. It doesn't use an intermediate temp
1196 directory because it doesn't need installation.
1197
1198 @param install_dir: destination directory for chromite installation.
1199 """
1200 local_devserver_dir = os.path.join(install_dir, 'devserver')
1201 git_repo = revision_control.GitRepo(local_devserver_dir, self._GIT_URL,
1202 abs_work_tree=local_devserver_dir)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001203 git_repo.reinit_repo_at(self.PROD_BRANCH)
beepsd9153b52013-01-23 20:52:46 -08001204
1205 if git_repo.get_latest_commit_hash():
1206 return True
1207 return False
Scott James Remnantbb1a9672014-03-03 14:03:09 -08001208
1209
1210class BtsocketRepo(_ExternalGitRepo):
1211 """Clones or updates the btsocket repo."""
1212
1213 _GIT_URL = ('https://chromium.googlesource.com/'
1214 'chromiumos/platform/btsocket')
1215
1216 def fetch(self, unused_dest_dir):
1217 """
1218 Fetch repo to a temporary location.
1219
1220 We use an intermediate temp directory because we have to build an
1221 egg for installation. If we can't get at the top commit hash after
1222 fetching something is wrong. This can happen when we've cloned/pulled
1223 an empty repo. Not something we expect to do.
1224
1225 @parma unused_dest_dir: passed in because we inherit from
1226 ExternalPackage.
1227
1228 @return: True if repo sync was successful.
1229 """
1230 self.temp_btsocket_dir = autotemp.tempdir(unique_id='btsocket')
1231 try:
1232 git_repo = revision_control.GitRepo(
1233 self.temp_btsocket_dir.name,
1234 self._GIT_URL,
1235 None,
1236 abs_work_tree=self.temp_btsocket_dir.name)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001237 git_repo.reinit_repo_at(self.PROD_BRANCH)
Scott James Remnantbb1a9672014-03-03 14:03:09 -08001238
1239 if git_repo.get_latest_commit_hash():
1240 return True
1241 except:
1242 self.temp_btsocket_dir.clean()
1243 raise
1244
1245 self.temp_btsocket_dir.clean()
1246 return False
1247
1248
1249 def build_and_install(self, install_dir):
1250 """
1251 Install the btsocket module using setup.py
1252
1253 @param install_dir: Target installation directory.
1254
1255 @return: A boolean indicating success of failure.
1256 """
1257 work_dir = os.getcwd()
1258 try:
1259 os.chdir(self.temp_btsocket_dir.name)
1260 rv = self._build_and_install_current_dir_setup_py(install_dir)
1261 finally:
1262 os.chdir(work_dir)
1263 self.temp_btsocket_dir.clean()
J. Richard Barnette428b3442014-07-22 11:38:55 -07001264 return rv