blob: 3683154c654a003c481e64dfc0addcc00b0d31ad [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
xixuanebe46312016-09-07 14:45:49 -07005from distutils.version import LooseVersion
6
Scott James Remnantbb1a9672014-03-03 14:03:09 -08007from autotest_lib.client.common_lib import autotemp, revision_control, utils
jamesrenff6e5aa2010-02-12 00:46:40 +00008
9_READ_SIZE = 64*1024
10_MAX_PACKAGE_SIZE = 100*1024*1024
11
12
13class Error(Exception):
14 """Local exception to be raised by code in this file."""
15
16class FetchError(Error):
17 """Failed to fetch a package from any of its listed URLs."""
18
19
20def _checksum_file(full_path):
21 """@returns The hex checksum of a file given its pathname."""
22 inputfile = open(full_path, 'rb')
23 try:
24 hex_sum = utils.hash('sha1', inputfile.read()).hexdigest()
25 finally:
26 inputfile.close()
27 return hex_sum
28
29
30def system(commandline):
Dan Shi57631dc2013-02-22 09:54:14 -080031 """Same as os.system(commandline) but logs the command first.
32
33 @param commandline: commandline to be called.
34 """
jamesrenff6e5aa2010-02-12 00:46:40 +000035 logging.info(commandline)
36 return os.system(commandline)
37
38
39def find_top_of_autotest_tree():
40 """@returns The full path to the top of the autotest directory tree."""
41 dirname = os.path.dirname(__file__)
42 autotest_dir = os.path.abspath(os.path.join(dirname, '..'))
43 return autotest_dir
44
45
46class ExternalPackage(object):
47 """
48 Defines an external package with URLs to fetch its sources from and
49 a build_and_install() method to unpack it, build it and install it
50 beneath our own autotest/site-packages directory.
51
52 Base Class. Subclass this to define packages.
beepsd5335852013-04-09 09:58:52 -070053 Note: Unless your subclass has a specific reason to, it should not
54 re-install the package every time build_externals is invoked, as this
55 happens periodically through the scheduler. To avoid doing so the is_needed
56 method needs to return an appropriate value.
jamesrenff6e5aa2010-02-12 00:46:40 +000057
58 Attributes:
59 @attribute urls - A tuple of URLs to try fetching the package from.
60 @attribute local_filename - A local filename to use when saving the
61 fetched package.
Allen Lia9f2a2b2016-08-03 13:44:50 -070062 @attribute dist_name - The name of the Python distribution. For example,
63 the package MySQLdb is included in the distribution named
64 MySQL-python. This is generally the PyPI name. Defaults to the
65 name part of the local_filename.
jamesrenff6e5aa2010-02-12 00:46:40 +000066 @attribute hex_sum - The hex digest (currently SHA1) of this package
67 to be used to verify its contents.
68 @attribute module_name - The installed python module name to be used for
69 for a version check. Defaults to the lower case class name with
70 the word Package stripped off.
xixuand63f4502016-09-12 17:36:55 -070071 @attribute extracted_package_path - The path to package directory after
72 extracting.
jamesrenff6e5aa2010-02-12 00:46:40 +000073 @attribute version - The desired minimum package version.
Michael Janssena7427612014-11-14 15:44:39 -080074 @attribute os_requirements - A dictionary mapping pathname tuples on the
jamesrenff6e5aa2010-02-12 00:46:40 +000075 the OS distribution to a likely name of a package the user
76 needs to install on their system in order to get this file.
Michael Janssena7427612014-11-14 15:44:39 -080077 One of the files in the tuple must exist.
jamesrenff6e5aa2010-02-12 00:46:40 +000078 @attribute name - Read only, the printable name of the package.
79 @attribute subclasses - This class attribute holds a list of all defined
80 subclasses. It is constructed dynamically using the metaclass.
81 """
Dan Shi16c0a502015-07-14 17:29:48 -070082 # Modules that are meant to be installed in system directory, rather than
83 # autotest/site-packages. These modules should be skipped if the module
84 # is already installed in system directory. This prevents an older version
85 # of the module from being installed in system directory.
86 SYSTEM_MODULES = ['setuptools']
87
jamesrenff6e5aa2010-02-12 00:46:40 +000088 subclasses = []
89 urls = ()
90 local_filename = None
Allen Lia9f2a2b2016-08-03 13:44:50 -070091 dist_name = None
jamesrenff6e5aa2010-02-12 00:46:40 +000092 hex_sum = None
93 module_name = None
94 version = None
95 os_requirements = None
96
97
98 class __metaclass__(type):
99 """Any time a subclass is defined, add it to our list."""
100 def __init__(mcs, name, bases, dict):
beepsd9153b52013-01-23 20:52:46 -0800101 if name != 'ExternalPackage' and not name.startswith('_'):
jamesrenff6e5aa2010-02-12 00:46:40 +0000102 mcs.subclasses.append(mcs)
103
104
105 def __init__(self):
106 self.verified_package = ''
107 if not self.module_name:
108 self.module_name = self.name.lower()
Allen Lia9f2a2b2016-08-03 13:44:50 -0700109 if not self.dist_name and self.local_filename:
110 self.dist_name = self.local_filename[:self.local_filename.rindex('-')]
jamesrenff6e5aa2010-02-12 00:46:40 +0000111 self.installed_version = ''
112
113
114 @property
xixuand63f4502016-09-12 17:36:55 -0700115 def extracted_package_path(self):
116 """Return the package path after extracting.
117
118 If the package has assigned its own extracted_package_path, use it.
119 Or use part of its local_filename as the extracting path.
120 """
121 return self.local_filename[:-len(self._get_extension(
122 self.local_filename))]
123
124
125 @property
jamesrenff6e5aa2010-02-12 00:46:40 +0000126 def name(self):
127 """Return the class name with any trailing 'Package' stripped off."""
128 class_name = self.__class__.__name__
129 if class_name.endswith('Package'):
130 return class_name[:-len('Package')]
131 return class_name
132
133
Dan Shi7b6297b2015-06-23 13:54:09 -0700134 def is_needed(self, install_dir):
beepsd5335852013-04-09 09:58:52 -0700135 """
136 Check to see if we need to reinstall a package. This is contingent on:
137 1. Module name: If the name of the module is different from the package,
138 the class that installs it needs to specify a module_name string,
139 so we can try importing the module.
140
141 2. Installed version: If the module doesn't contain a __version__ the
142 class that installs it needs to override the
143 _get_installed_version_from_module method to return an appropriate
144 version string.
145
146 3. Version/Minimum version: The class that installs the package should
147 contain a version string, and an optional minimum version string.
Dan Shi57631dc2013-02-22 09:54:14 -0800148
Dan Shi7b6297b2015-06-23 13:54:09 -0700149 4. install_dir: If the module exists in a different directory, e.g.,
150 /usr/lib/python2.7/dist-packages/, the module will be forced to be
151 installed in install_dir.
152
153 @param install_dir: install directory.
beepsd5335852013-04-09 09:58:52 -0700154 @returns True if self.module_name needs to be built and installed.
Dan Shi57631dc2013-02-22 09:54:14 -0800155 """
jamesrenff6e5aa2010-02-12 00:46:40 +0000156 if not self.module_name or not self.version:
157 logging.warning('version and module_name required for '
158 'is_needed() check to work.')
159 return True
160 try:
161 module = __import__(self.module_name)
162 except ImportError, e:
jamesrenca2a9002010-04-20 22:33:22 +0000163 logging.info("%s isn't present. Will install.", self.module_name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000164 return True
Dan Shi16c0a502015-07-14 17:29:48 -0700165 if (not module.__file__.startswith(install_dir) and
166 not self.module_name in self.SYSTEM_MODULES):
Dan Shi7b6297b2015-06-23 13:54:09 -0700167 logging.info('Module %s is installed in %s, rather than %s. The '
168 'module will be forced to be installed in %s.',
169 self.module_name, module.__file__, install_dir,
170 install_dir)
171 return True
jamesrenff6e5aa2010-02-12 00:46:40 +0000172 self.installed_version = self._get_installed_version_from_module(module)
xixuanebe46312016-09-07 14:45:49 -0700173 if not self.installed_version:
174 return True
175
jamesrenff6e5aa2010-02-12 00:46:40 +0000176 logging.info('imported %s version %s.', self.module_name,
177 self.installed_version)
Dale Curtis74a314b2011-06-23 14:55:46 -0700178 if hasattr(self, 'minimum_version'):
xixuanebe46312016-09-07 14:45:49 -0700179 return LooseVersion(self.minimum_version) > LooseVersion(
180 self.installed_version)
Dale Curtis74a314b2011-06-23 14:55:46 -0700181 else:
xixuanebe46312016-09-07 14:45:49 -0700182 return LooseVersion(self.version) > LooseVersion(
183 self.installed_version)
jamesrenff6e5aa2010-02-12 00:46:40 +0000184
185
186 def _get_installed_version_from_module(self, module):
187 """Ask our module its version string and return it or '' if unknown."""
188 try:
189 return module.__version__
190 except AttributeError:
191 logging.error('could not get version from %s', module)
192 return ''
193
194
195 def _build_and_install(self, install_dir):
196 """Subclasses MUST provide their own implementation."""
197 raise NotImplementedError
198
199
200 def _build_and_install_current_dir(self, install_dir):
201 """
202 Subclasses that use _build_and_install_from_package() MUST provide
203 their own implementation of this method.
204 """
205 raise NotImplementedError
206
207
208 def build_and_install(self, install_dir):
209 """
210 Builds and installs the package. It must have been fetched already.
211
212 @param install_dir - The package installation directory. If it does
213 not exist it will be created.
214 """
215 if not self.verified_package:
216 raise Error('Must call fetch() first. - %s' % self.name)
217 self._check_os_requirements()
218 return self._build_and_install(install_dir)
219
220
221 def _check_os_requirements(self):
222 if not self.os_requirements:
223 return
224 failed = False
Michael Janssena7427612014-11-14 15:44:39 -0800225 for file_names, package_name in self.os_requirements.iteritems():
226 if not any(os.path.exists(file_name) for file_name in file_names):
jamesrenff6e5aa2010-02-12 00:46:40 +0000227 failed = True
Michael Janssena7427612014-11-14 15:44:39 -0800228 logging.error('Can\'t find %s, %s probably needs it.',
J. Richard Barnette6f7606b2015-06-11 17:26:23 -0700229 ' or '.join(file_names), self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000230 logging.error('Perhaps you need to install something similar '
231 'to the %s package for OS first.', package_name)
232 if failed:
233 raise Error('Missing OS requirements for %s. (see above)' %
234 self.name)
235
236
237 def _build_and_install_current_dir_setup_py(self, install_dir):
238 """For use as a _build_and_install_current_dir implementation."""
239 egg_path = self._build_egg_using_setup_py(setup_py='setup.py')
240 if not egg_path:
241 return False
242 return self._install_from_egg(install_dir, egg_path)
243
244
245 def _build_and_install_current_dir_setupegg_py(self, install_dir):
246 """For use as a _build_and_install_current_dir implementation."""
247 egg_path = self._build_egg_using_setup_py(setup_py='setupegg.py')
248 if not egg_path:
249 return False
250 return self._install_from_egg(install_dir, egg_path)
251
252
253 def _build_and_install_current_dir_noegg(self, install_dir):
254 if not self._build_using_setup_py():
255 return False
256 return self._install_using_setup_py_and_rsync(install_dir)
257
258
xixuand63f4502016-09-12 17:36:55 -0700259 def _get_extension(self, package):
260 """Get extension of package."""
261 valid_package_extensions = ['.tar.gz', '.tar.bz2', '.zip']
262 extension = None
263
264 for ext in valid_package_extensions:
265 if package.endswith(ext):
266 extension = ext
267 break
268
269 if not extension:
270 raise Error('Unexpected package file extension on %s' % package)
271
272 return extension
273
274
jamesrenff6e5aa2010-02-12 00:46:40 +0000275 def _build_and_install_from_package(self, install_dir):
276 """
277 This method may be used as a _build_and_install() implementation
278 for subclasses if they implement _build_and_install_current_dir().
279
280 Extracts the .tar.gz file, chdirs into the extracted directory
281 (which is assumed to match the tar filename) and calls
282 _build_and_isntall_current_dir from there.
283
284 Afterwards the build (regardless of failure) extracted .tar.gz
285 directory is cleaned up.
286
287 @returns True on success, False otherwise.
288
289 @raises OSError If the expected extraction directory does not exist.
290 """
291 self._extract_compressed_package()
xixuand63f4502016-09-12 17:36:55 -0700292 extension = self._get_extension(self.verified_package)
jamesrenff6e5aa2010-02-12 00:46:40 +0000293 os.chdir(os.path.dirname(self.verified_package))
xixuand63f4502016-09-12 17:36:55 -0700294 os.chdir(self.extracted_package_path)
jamesrenff6e5aa2010-02-12 00:46:40 +0000295 extracted_dir = os.getcwd()
296 try:
297 return self._build_and_install_current_dir(install_dir)
298 finally:
299 os.chdir(os.path.join(extracted_dir, '..'))
300 shutil.rmtree(extracted_dir)
301
302
303 def _extract_compressed_package(self):
304 """Extract the fetched compressed .tar or .zip within its directory."""
305 if not self.verified_package:
306 raise Error('Package must have been fetched first.')
307 os.chdir(os.path.dirname(self.verified_package))
308 if self.verified_package.endswith('gz'):
309 status = system("tar -xzf '%s'" % self.verified_package)
310 elif self.verified_package.endswith('bz2'):
311 status = system("tar -xjf '%s'" % self.verified_package)
312 elif self.verified_package.endswith('zip'):
313 status = system("unzip '%s'" % self.verified_package)
314 else:
315 raise Error('Unknown compression suffix on %s.' %
316 self.verified_package)
317 if status:
318 raise Error('tar failed with %s' % (status,))
319
320
321 def _build_using_setup_py(self, setup_py='setup.py'):
322 """
323 Assuming the cwd is the extracted python package, execute a simple
324 python setup.py build.
325
326 @param setup_py - The name of the setup.py file to execute.
327
328 @returns True on success, False otherwise.
329 """
330 if not os.path.exists(setup_py):
331 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
332 status = system("'%s' %s build" % (sys.executable, setup_py))
333 if status:
Dan Shi57631dc2013-02-22 09:54:14 -0800334 logging.error('%s build failed.', self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000335 return False
336 return True
337
338
339 def _build_egg_using_setup_py(self, setup_py='setup.py'):
340 """
341 Assuming the cwd is the extracted python package, execute a simple
342 python setup.py bdist_egg.
343
344 @param setup_py - The name of the setup.py file to execute.
345
346 @returns The relative path to the resulting egg file or '' on failure.
347 """
348 if not os.path.exists(setup_py):
349 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
350 egg_subdir = 'dist'
351 if os.path.isdir(egg_subdir):
352 shutil.rmtree(egg_subdir)
353 status = system("'%s' %s bdist_egg" % (sys.executable, setup_py))
354 if status:
355 logging.error('bdist_egg of setuptools failed.')
356 return ''
357 # I've never seen a bdist_egg lay multiple .egg files.
358 for filename in os.listdir(egg_subdir):
359 if filename.endswith('.egg'):
360 return os.path.join(egg_subdir, filename)
361
362
363 def _install_from_egg(self, install_dir, egg_path):
364 """
365 Install a module from an egg file by unzipping the necessary parts
366 into install_dir.
367
368 @param install_dir - The installation directory.
369 @param egg_path - The pathname of the egg file.
370 """
371 status = system("unzip -q -o -d '%s' '%s'" % (install_dir, egg_path))
372 if status:
373 logging.error('unzip of %s failed', egg_path)
374 return False
Allen Lia9f2a2b2016-08-03 13:44:50 -0700375 egg_info_dir = os.path.join(install_dir, 'EGG-INFO')
376 if os.path.isdir(egg_info_dir):
377 egg_info_new_path = self._get_egg_info_path(install_dir)
378 if egg_info_new_path:
379 if os.path.exists(egg_info_new_path):
380 shutil.rmtree(egg_info_new_path)
381 os.rename(egg_info_dir, egg_info_new_path)
382 else:
383 shutil.rmtree(egg_info_dir)
jamesrenff6e5aa2010-02-12 00:46:40 +0000384 return True
385
386
Allen Lia9f2a2b2016-08-03 13:44:50 -0700387 def _get_egg_info_path(self, install_dir):
388 """Get egg-info path for this package.
389
390 Example path: install_dir/MySQL_python-1.2.3.egg-info
391
392 """
393 if self.dist_name:
394 egg_info_name_part = self.dist_name.replace('-', '_')
395 if self.version:
396 egg_info_filename = '%s-%s.egg-info' % (egg_info_name_part,
397 self.version)
398 else:
399 egg_info_filename = '%s.egg-info' % (egg_info_name_part,)
400 return os.path.join(install_dir, egg_info_filename)
401 else:
402 return None
403
404
jamesrenff6e5aa2010-02-12 00:46:40 +0000405 def _get_temp_dir(self):
406 return tempfile.mkdtemp(dir='/var/tmp')
407
408
409 def _site_packages_path(self, temp_dir):
410 # This makes assumptions about what python setup.py install
411 # does when given a prefix. Is this always correct?
412 python_xy = 'python%s' % sys.version[:3]
413 return os.path.join(temp_dir, 'lib', python_xy, 'site-packages')
414
415
beepsd9153b52013-01-23 20:52:46 -0800416 def _rsync (self, temp_site_dir, install_dir):
417 """Rsync contents. """
418 status = system("rsync -r '%s/' '%s/'" %
419 (os.path.normpath(temp_site_dir),
420 os.path.normpath(install_dir)))
421 if status:
422 logging.error('%s rsync to install_dir failed.', self.name)
423 return False
424 return True
425
426
jamesrenff6e5aa2010-02-12 00:46:40 +0000427 def _install_using_setup_py_and_rsync(self, install_dir,
428 setup_py='setup.py',
429 temp_dir=None):
430 """
431 Assuming the cwd is the extracted python package, execute a simple:
432
433 python setup.py install --prefix=BLA
434
435 BLA will be a temporary directory that everything installed will
436 be picked out of and rsynced to the appropriate place under
437 install_dir afterwards.
438
439 Afterwards, it deconstructs the extra lib/pythonX.Y/site-packages/
440 directory tree that setuptools created and moves all installed
441 site-packages directly up into install_dir itself.
442
443 @param install_dir the directory for the install to happen under.
444 @param setup_py - The name of the setup.py file to execute.
445
446 @returns True on success, False otherwise.
447 """
448 if not os.path.exists(setup_py):
449 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
450
451 if temp_dir is None:
452 temp_dir = self._get_temp_dir()
453
454 try:
455 status = system("'%s' %s install --no-compile --prefix='%s'"
456 % (sys.executable, setup_py, temp_dir))
457 if status:
Dan Shi57631dc2013-02-22 09:54:14 -0800458 logging.error('%s install failed.', self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000459 return False
460
461 if os.path.isdir(os.path.join(temp_dir, 'lib')):
462 # NOTE: This ignores anything outside of the lib/ dir that
463 # was installed.
464 temp_site_dir = self._site_packages_path(temp_dir)
465 else:
466 temp_site_dir = temp_dir
467
beepsd9153b52013-01-23 20:52:46 -0800468 return self._rsync(temp_site_dir, install_dir)
jamesrenff6e5aa2010-02-12 00:46:40 +0000469 finally:
470 shutil.rmtree(temp_dir)
471
472
473
474 def _build_using_make(self, install_dir):
475 """Build the current package using configure/make.
476
477 @returns True on success, False otherwise.
478 """
479 install_prefix = os.path.join(install_dir, 'usr', 'local')
480 status = system('./configure --prefix=%s' % install_prefix)
481 if status:
482 logging.error('./configure failed for %s', self.name)
483 return False
484 status = system('make')
485 if status:
486 logging.error('make failed for %s', self.name)
487 return False
488 status = system('make check')
489 if status:
490 logging.error('make check failed for %s', self.name)
491 return False
492 return True
493
494
495 def _install_using_make(self):
496 """Install the current package using make install.
497
498 Assumes the install path was set up while running ./configure (in
499 _build_using_make()).
500
501 @returns True on success, False otherwise.
502 """
503 status = system('make install')
504 return status == 0
505
506
507 def fetch(self, dest_dir):
508 """
509 Fetch the package from one its URLs and save it in dest_dir.
510
511 If the the package already exists in dest_dir and the checksum
512 matches this code will not fetch it again.
513
514 Sets the 'verified_package' attribute with the destination pathname.
515
516 @param dest_dir - The destination directory to save the local file.
517 If it does not exist it will be created.
518
519 @returns A boolean indicating if we the package is now in dest_dir.
520 @raises FetchError - When something unexpected happens.
521 """
522 if not os.path.exists(dest_dir):
523 os.makedirs(dest_dir)
524 local_path = os.path.join(dest_dir, self.local_filename)
525
526 # If the package exists, verify its checksum and be happy if it is good.
527 if os.path.exists(local_path):
528 actual_hex_sum = _checksum_file(local_path)
529 if self.hex_sum == actual_hex_sum:
530 logging.info('Good checksum for existing %s package.',
531 self.name)
532 self.verified_package = local_path
533 return True
534 logging.warning('Bad checksum for existing %s package. '
535 'Re-downloading', self.name)
536 os.rename(local_path, local_path + '.wrong-checksum')
537
538 # Download the package from one of its urls, rejecting any if the
539 # checksum does not match.
540 for url in self.urls:
541 logging.info('Fetching %s', url)
542 try:
543 url_file = urllib2.urlopen(url)
544 except (urllib2.URLError, EnvironmentError):
545 logging.warning('Could not fetch %s package from %s.',
546 self.name, url)
547 continue
Dan Shi57631dc2013-02-22 09:54:14 -0800548
jamesrenff6e5aa2010-02-12 00:46:40 +0000549 data_length = int(url_file.info().get('Content-Length',
550 _MAX_PACKAGE_SIZE))
551 if data_length <= 0 or data_length > _MAX_PACKAGE_SIZE:
552 raise FetchError('%s from %s fails Content-Length %d '
553 'sanity check.' % (self.name, url,
554 data_length))
555 checksum = utils.hash('sha1')
556 total_read = 0
557 output = open(local_path, 'wb')
558 try:
559 while total_read < data_length:
560 data = url_file.read(_READ_SIZE)
561 if not data:
562 break
563 output.write(data)
564 checksum.update(data)
565 total_read += len(data)
566 finally:
567 output.close()
568 if self.hex_sum != checksum.hexdigest():
569 logging.warning('Bad checksum for %s fetched from %s.',
570 self.name, url)
571 logging.warning('Got %s', checksum.hexdigest())
572 logging.warning('Expected %s', self.hex_sum)
573 os.unlink(local_path)
574 continue
575 logging.info('Good checksum.')
576 self.verified_package = local_path
577 return True
578 else:
579 return False
580
581
582# NOTE: This class definition must come -before- all other ExternalPackage
583# classes that need to use this version of setuptools so that is is inserted
584# into the ExternalPackage.subclasses list before them.
585class SetuptoolsPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800586 """setuptools package"""
jamesrenff6e5aa2010-02-12 00:46:40 +0000587 # For all known setuptools releases a string compare works for the
588 # version string. Hopefully they never release a 0.10. (Their own
589 # version comparison code would break if they did.)
Dan Shi16c0a502015-07-14 17:29:48 -0700590 # Any system with setuptools > 18.0.1 is fine. If none installed, then
Dale Curtis74a314b2011-06-23 14:55:46 -0700591 # try to install the latest found on the upstream.
Dan Shi16c0a502015-07-14 17:29:48 -0700592 minimum_version = '18.0.1'
593 version = '18.0.1'
jamesrenff6e5aa2010-02-12 00:46:40 +0000594 urls = ('http://pypi.python.org/packages/source/s/setuptools/'
595 'setuptools-%s.tar.gz' % (version,),)
596 local_filename = 'setuptools-%s.tar.gz' % version
Dan Shi16c0a502015-07-14 17:29:48 -0700597 hex_sum = 'ebc4fe81b7f6d61d923d9519f589903824044f52'
jamesrenff6e5aa2010-02-12 00:46:40 +0000598
599 SUDO_SLEEP_DELAY = 15
600
601
602 def _build_and_install(self, install_dir):
603 """Install setuptools on the system."""
604 logging.info('NOTE: setuptools install does not use install_dir.')
605 return self._build_and_install_from_package(install_dir)
606
607
608 def _build_and_install_current_dir(self, install_dir):
609 egg_path = self._build_egg_using_setup_py()
610 if not egg_path:
611 return False
612
613 print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n'
614 print 'About to run sudo to install setuptools', self.version
615 print 'on your system for use by', sys.executable, '\n'
616 print '!! ^C within', self.SUDO_SLEEP_DELAY, 'seconds to abort.\n'
617 time.sleep(self.SUDO_SLEEP_DELAY)
618
619 # Copy the egg to the local filesystem /var/tmp so that root can
620 # access it properly (avoid NFS squashroot issues).
621 temp_dir = self._get_temp_dir()
622 try:
623 shutil.copy(egg_path, temp_dir)
624 egg_name = os.path.split(egg_path)[1]
625 temp_egg = os.path.join(temp_dir, egg_name)
626 p = subprocess.Popen(['sudo', '/bin/sh', temp_egg],
627 stdout=subprocess.PIPE)
628 regex = re.compile('Copying (.*?) to (.*?)\n')
629 match = regex.search(p.communicate()[0])
630 status = p.wait()
631
632 if match:
633 compiled = os.path.join(match.group(2), match.group(1))
634 os.system("sudo chmod a+r '%s'" % compiled)
635 finally:
636 shutil.rmtree(temp_dir)
637
638 if status:
639 logging.error('install of setuptools from egg failed.')
640 return False
641 return True
642
643
644class MySQLdbPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800645 """mysql package, used in scheduler."""
jamesrenff6e5aa2010-02-12 00:46:40 +0000646 module_name = 'MySQLdb'
Alex Miller47d61282013-04-17 13:53:58 -0700647 version = '1.2.3'
jamesrenff6e5aa2010-02-12 00:46:40 +0000648 urls = ('http://downloads.sourceforge.net/project/mysql-python/'
649 'mysql-python/%(version)s/MySQL-python-%(version)s.tar.gz'
650 % dict(version=version),)
651 local_filename = 'MySQL-python-%s.tar.gz' % version
Alex Miller47d61282013-04-17 13:53:58 -0700652 hex_sum = '3511bb8c57c6016eeafa531d5c3ea4b548915e3c'
jamesrenff6e5aa2010-02-12 00:46:40 +0000653
654 _build_and_install_current_dir = (
655 ExternalPackage._build_and_install_current_dir_setup_py)
656
657
658 def _build_and_install(self, install_dir):
659 if not os.path.exists('/usr/bin/mysql_config'):
Dan Shi16c0a502015-07-14 17:29:48 -0700660 error_msg = ('You need to install /usr/bin/mysql_config.\n'
661 'On Ubuntu or Debian based systems use this: '
662 'sudo apt-get install libmysqlclient15-dev')
663 logging.error(error_msg)
664 return False, error_msg
jamesrenff6e5aa2010-02-12 00:46:40 +0000665 return self._build_and_install_from_package(install_dir)
666
667
668class DjangoPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800669 """django package."""
Alex Miller47d61282013-04-17 13:53:58 -0700670 version = '1.5.1'
jamesrenff6e5aa2010-02-12 00:46:40 +0000671 local_filename = 'Django-%s.tar.gz' % version
672 urls = ('http://www.djangoproject.com/download/%s/tarball/' % version,)
Alex Miller47d61282013-04-17 13:53:58 -0700673 hex_sum = '0ab97b90c4c79636e56337f426f1e875faccbba1'
jamesrenff6e5aa2010-02-12 00:46:40 +0000674
675 _build_and_install = ExternalPackage._build_and_install_from_package
676 _build_and_install_current_dir = (
677 ExternalPackage._build_and_install_current_dir_noegg)
678
679
680 def _get_installed_version_from_module(self, module):
681 try:
682 return module.get_version().split()[0]
683 except AttributeError:
684 return '0.9.6'
685
686
687
688class NumpyPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800689 """numpy package, required by matploglib."""
Scott Zawalskic2c9a012013-03-06 09:13:26 -0500690 version = '1.7.0'
jamesrenff6e5aa2010-02-12 00:46:40 +0000691 local_filename = 'numpy-%s.tar.gz' % version
692 urls = ('http://downloads.sourceforge.net/project/numpy/NumPy/%(version)s/'
693 'numpy-%(version)s.tar.gz' % dict(version=version),)
Scott Zawalskic2c9a012013-03-06 09:13:26 -0500694 hex_sum = 'ba328985f20390b0f969a5be2a6e1141d5752cf9'
jamesrenff6e5aa2010-02-12 00:46:40 +0000695
696 _build_and_install = ExternalPackage._build_and_install_from_package
697 _build_and_install_current_dir = (
698 ExternalPackage._build_and_install_current_dir_setupegg_py)
699
700
jamesrenff6e5aa2010-02-12 00:46:40 +0000701class MatplotlibPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800702 """
703 matplotlib package
704
705 This requires numpy so it must be declared after numpy to guarantee that
706 it is already installed.
707 """
jamesrenff6e5aa2010-02-12 00:46:40 +0000708 version = '0.98.5.3'
709 short_version = '0.98.5'
710 local_filename = 'matplotlib-%s.tar.gz' % version
711 urls = ('http://downloads.sourceforge.net/project/matplotlib/matplotlib/'
712 'matplotlib-%s/matplotlib-%s.tar.gz' % (short_version, version),)
713 hex_sum = '2f6c894cf407192b3b60351bcc6468c0385d47b6'
Michael Janssena7427612014-11-14 15:44:39 -0800714 os_requirements = {('/usr/include/freetype2/ft2build.h',
715 '/usr/include/ft2build.h'): 'libfreetype6-dev',
716 ('/usr/include/png.h'): 'libpng12-dev'}
jamesrenff6e5aa2010-02-12 00:46:40 +0000717
718 _build_and_install = ExternalPackage._build_and_install_from_package
719 _build_and_install_current_dir = (
720 ExternalPackage._build_and_install_current_dir_setupegg_py)
721
722
beeps32a63082013-08-22 14:02:29 -0700723class JsonRPCLib(ExternalPackage):
724 """jsonrpclib package"""
725 version = '0.1.3'
726 module_name = 'jsonrpclib'
727 local_filename = '%s-%s.tar.gz' % (module_name, version)
728 urls = ('http://pypi.python.org/packages/source/j/%s/%s' %
729 (module_name, local_filename), )
730 hex_sum = '431714ed19ab677f641ce5d678a6a95016f5c452'
731
732 def _get_installed_version_from_module(self, module):
733 # jsonrpclib doesn't contain a proper version
734 return self.version
735
736 _build_and_install = ExternalPackage._build_and_install_from_package
737 _build_and_install_current_dir = (
738 ExternalPackage._build_and_install_current_dir_noegg)
739
740
jamesrenff6e5aa2010-02-12 00:46:40 +0000741class GwtPackage(ExternalPackage):
742 """Fetch and extract a local copy of GWT used to build the frontend."""
743
Dale Curtis74a314b2011-06-23 14:55:46 -0700744 version = '2.3.0'
jamesren012d0322010-04-30 20:21:29 +0000745 local_filename = 'gwt-%s.zip' % version
xixuand63f4502016-09-12 17:36:55 -0700746 urls = ('https://storage.googleapis.com/google-code-archive-downloads/'
747 'v2/code.google.com/google-web-toolkit/' + local_filename,)
Dale Curtis74a314b2011-06-23 14:55:46 -0700748 hex_sum = 'd51fce9166e6b31349659ffca89baf93e39bc84b'
jamesrenff6e5aa2010-02-12 00:46:40 +0000749 name = 'gwt'
750 about_filename = 'about.txt'
751 module_name = None # Not a Python module.
752
753
754 def is_needed(self, install_dir):
755 gwt_dir = os.path.join(install_dir, self.name)
756 about_file = os.path.join(install_dir, self.name, self.about_filename)
757
758 if not os.path.exists(gwt_dir) or not os.path.exists(about_file):
759 logging.info('gwt not installed for autotest')
760 return True
761
762 f = open(about_file, 'r')
763 version_line = f.readline()
764 f.close()
765
766 match = re.match(r'Google Web Toolkit (.*)', version_line)
767 if not match:
768 logging.info('did not find gwt version')
769 return True
770
771 logging.info('found gwt version %s', match.group(1))
772 return match.group(1) != self.version
773
774
jamesrenca2a9002010-04-20 22:33:22 +0000775 def _build_and_install(self, install_dir):
jamesrenff6e5aa2010-02-12 00:46:40 +0000776 os.chdir(install_dir)
777 self._extract_compressed_package()
jamesren012d0322010-04-30 20:21:29 +0000778 extracted_dir = self.local_filename[:-len('.zip')]
jamesrenff6e5aa2010-02-12 00:46:40 +0000779 target_dir = os.path.join(install_dir, self.name)
780 if os.path.exists(target_dir):
781 shutil.rmtree(target_dir)
782 os.rename(extracted_dir, target_dir)
783 return True
784
785
Alex Millere7b6e8b2013-02-13 17:34:04 -0800786class PyudevPackage(ExternalPackage):
787 """
788 pyudev module
789
790 Used in unittests.
791 """
792 version = '0.16.1'
793 url_filename = 'pyudev-%s.tar.gz' % version
794 local_filename = url_filename
795 urls = ('http://pypi.python.org/packages/source/p/pyudev/%s' % (
796 url_filename),)
797 hex_sum = 'b36bc5c553ce9b56d32a5e45063a2c88156771c0'
798
799 _build_and_install = ExternalPackage._build_and_install_from_package
800 _build_and_install_current_dir = (
801 ExternalPackage._build_and_install_current_dir_setup_py)
802
803
804class PyMoxPackage(ExternalPackage):
805 """
806 mox module
807
808 Used in unittests.
809 """
810 module_name = 'mox'
811 version = '0.5.3'
812 url_filename = 'mox-%s.tar.gz' % version
813 local_filename = url_filename
814 urls = ('http://pypi.python.org/packages/source/m/mox/%s' % (
815 url_filename),)
816 hex_sum = '1c502d2c0a8aefbba2c7f385a83d33e7d822452a'
817
818 _build_and_install = ExternalPackage._build_and_install_from_package
819 _build_and_install_current_dir = (
820 ExternalPackage._build_and_install_current_dir_noegg)
821
822 def _get_installed_version_from_module(self, module):
823 # mox doesn't contain a proper version
824 return self.version
825
826
Jason Abele2371d1a2013-11-22 12:18:18 -0800827class PySeleniumPackage(ExternalPackage):
828 """
829 selenium module
830
831 Used in wifi_interop suite.
832 """
833 module_name = 'selenium'
834 version = '2.37.2'
835 url_filename = 'selenium-%s.tar.gz' % version
836 local_filename = url_filename
837 urls = ('https://pypi.python.org/packages/source/s/selenium/%s' % (
838 url_filename),)
839 hex_sum = '66946d5349e36d946daaad625c83c30c11609e36'
840
841 _build_and_install = ExternalPackage._build_and_install_from_package
842 _build_and_install_current_dir = (
843 ExternalPackage._build_and_install_current_dir_setup_py)
844
845
Simran Basid6b83772014-01-06 16:31:30 -0800846class FaultHandlerPackage(ExternalPackage):
847 """
848 faulthandler module
849 """
850 module_name = 'faulthandler'
851 version = '2.3'
852 url_filename = '%s-%s.tar.gz' % (module_name, version)
853 local_filename = url_filename
854 urls = ('http://pypi.python.org/packages/source/f/faulthandler/%s' %
855 (url_filename),)
856 hex_sum = 'efb30c068414fba9df892e48fcf86170cbf53589'
857
858 _build_and_install = ExternalPackage._build_and_install_from_package
859 _build_and_install_current_dir = (
860 ExternalPackage._build_and_install_current_dir_noegg)
861
862
Alex Millera87e5482014-06-09 16:04:49 -0700863class PsutilPackage(ExternalPackage):
864 """
865 psutil module
866 """
867 module_name = 'psutil'
868 version = '2.1.1'
869 url_filename = '%s-%s.tar.gz' % (module_name, version)
870 local_filename = url_filename
871 urls = ('http://pypi.python.org/packages/source/p/psutil/%s' %
872 (url_filename),)
873 hex_sum = '0c20a20ed316e69f2b0881530439213988229916'
874
875 _build_and_install = ExternalPackage._build_and_install_from_package
876 _build_and_install_current_dir = (
877 ExternalPackage._build_and_install_current_dir_setup_py)
878
879
Michael Liangd2d294c2014-06-24 15:24:49 -0700880class ElasticSearchPackage(ExternalPackage):
881 """elasticsearch-py package."""
Dan Shicae83c72015-07-22 17:58:21 -0700882 version = '1.6.0'
Michael Liangd2d294c2014-06-24 15:24:49 -0700883 url_filename = 'elasticsearch-%s.tar.gz' % version
884 local_filename = url_filename
Michael Liang5934b712014-08-12 15:45:14 -0700885 urls = ('https://pypi.python.org/packages/source/e/elasticsearch/%s' %
886 (url_filename),)
Dan Shicae83c72015-07-22 17:58:21 -0700887 hex_sum = '3e676c96f47935b1f52df82df3969564bd356b1c'
Michael Liangd2d294c2014-06-24 15:24:49 -0700888 _build_and_install = ExternalPackage._build_and_install_from_package
889 _build_and_install_current_dir = (
890 ExternalPackage._build_and_install_current_dir_setup_py)
891
xixuanebe46312016-09-07 14:45:49 -0700892 def _get_installed_version_from_module(self, module):
893 # Elastic's version format is like tuple (1, 6, 0), which needs to be
894 # transferred to 1.6.0.
895 try:
896 return '.'.join(str(i) for i in module.__version__)
897 except:
898 return self.version
899
Michael Liangd2d294c2014-06-24 15:24:49 -0700900
Michael Liang5934b712014-08-12 15:45:14 -0700901class Urllib3Package(ExternalPackage):
902 """elasticsearch-py package."""
903 version = '1.9'
904 url_filename = 'urllib3-%s.tar.gz' % version
905 local_filename = url_filename
906 urls = ('https://pypi.python.org/packages/source/u/urllib3/%s' %
907 (url_filename),)
908 hex_sum = '9522197efb2a2b49ce804de3a515f06d97b6602f'
909 _build_and_install = ExternalPackage._build_and_install_from_package
910 _build_and_install_current_dir = (
911 ExternalPackage._build_and_install_current_dir_setup_py)
912
Wai-Hong Tam8ce47ea2017-05-12 11:33:43 -0700913class ImagingLibraryPackage(ExternalPackage):
914 """Python Imaging Library (PIL)."""
915 version = '1.1.7'
916 url_filename = 'Imaging-%s.tar.gz' % version
917 local_filename = url_filename
918 urls = ('http://commondatastorage.googleapis.com/chromeos-mirror/gentoo/'
919 'distfiles/%s' % url_filename,)
920 hex_sum = '76c37504251171fda8da8e63ecb8bc42a69a5c81'
921
922 def _build_and_install(self, install_dir):
923 #The path of zlib library might be different from what PIL setup.py is
924 #expected. Following change does the best attempt to link the library
925 #to a path PIL setup.py will try.
926 libz_possible_path = '/usr/lib/x86_64-linux-gnu/libz.so'
927 libz_expected_path = '/usr/lib/libz.so'
928 if (os.path.exists(libz_possible_path) and
929 not os.path.exists(libz_expected_path)):
930 utils.run('sudo ln -s %s %s' %
931 (libz_possible_path, libz_expected_path))
932 return self._build_and_install_from_package(install_dir)
933
934 _build_and_install_current_dir = (
935 ExternalPackage._build_and_install_current_dir_noegg)
J. Richard Barnette428b3442014-07-22 11:38:55 -0700936
937
Allen Lia9f2a2b2016-08-03 13:44:50 -0700938class AstroidPackage(ExternalPackage):
939 """astroid package."""
Allen Li4c275d22017-07-19 11:56:24 -0700940 version = '1.5.3'
Allen Lia9f2a2b2016-08-03 13:44:50 -0700941 url_filename = 'astroid-%s.tar.gz' % version
942 local_filename = url_filename
Allen Li4c275d22017-07-19 11:56:24 -0700943 #md5=6f65e4ea8290ec032320460905afb828
944 urls = ('https://pypi.python.org/packages/d7/b7/'
945 '112288f75293d6f12b1e41bac1e822fd0f29b0f88e2c4378cdd295b9d838/%s' %
Allen Lia9f2a2b2016-08-03 13:44:50 -0700946 (url_filename),)
Allen Li4c275d22017-07-19 11:56:24 -0700947 hex_sum = 'e654225ab5bd2788e5e246b156910990bf33cde6'
948 _build_and_install = ExternalPackage._build_and_install_from_package
949 _build_and_install_current_dir = (
950 ExternalPackage._build_and_install_current_dir_setup_py)
951
952
953class LazyObjectProxyPackage(ExternalPackage):
954 """lazy-object-proxy package (dependency for astroid)."""
955 version = '1.3.1'
956 url_filename = 'lazy-object-proxy-%s.tar.gz' % version
957 local_filename = url_filename
958 #md5=e128152b76eb5b9ba759504936139fd0
959 urls = ('https://pypi.python.org/packages/55/08/'
960 '23c0753599bdec1aec273e322f277c4e875150325f565017f6280549f554/%s' %
961 (url_filename),)
962 hex_sum = '984828d8f672986ca926373986214d7057b772fb'
963 _build_and_install = ExternalPackage._build_and_install_from_package
964 _build_and_install_current_dir = (
965 ExternalPackage._build_and_install_current_dir_setup_py)
966
967
968class SingleDispatchPackage(ExternalPackage):
969 """singledispatch package (dependency for astroid)."""
970 version = '3.4.0.3'
971 url_filename = 'singledispatch-%s.tar.gz' % version
972 local_filename = url_filename
973 #md5=af2fc6a3d6cc5a02d0bf54d909785fcb
974 urls = ('https://pypi.python.org/packages/d9/e9/'
975 '513ad8dc17210db12cb14f2d4d190d618fb87dd38814203ea71c87ba5b68/%s' %
976 (url_filename),)
977 hex_sum = 'f93241b06754a612af8bb7aa208c4d1805637022'
978 _build_and_install = ExternalPackage._build_and_install_from_package
979 _build_and_install_current_dir = (
980 ExternalPackage._build_and_install_current_dir_setup_py)
981
982
983class Enum34Package(ExternalPackage):
984 """enum34 package (dependency for astroid)."""
985 version = '1.1.6'
986 url_filename = 'enum34-%s.tar.gz' % version
987 local_filename = url_filename
988 #md5=5f13a0841a61f7fc295c514490d120d0
989 urls = ('https://pypi.python.org/packages/bf/3e/'
990 '31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/%s' %
991 (url_filename),)
992 hex_sum = '014ef5878333ff91099893d615192c8cd0b1525a'
993 _build_and_install = ExternalPackage._build_and_install_from_package
994 _build_and_install_current_dir = (
995 ExternalPackage._build_and_install_current_dir_setup_py)
996
997
998class WraptPackage(ExternalPackage):
999 """wrapt package (dependency for astroid)."""
1000 version = '1.10.10'
1001 url_filename = 'wrapt-%s.tar.gz' % version
1002 local_filename = url_filename
1003 #md5=97365e906afa8b431f266866ec4e2e18
1004 urls = ('https://pypi.python.org/packages/a3/bb/'
1005 '525e9de0a220060394f4aa34fdf6200853581803d92714ae41fc3556e7d7/%s' %
1006 (url_filename),)
1007 hex_sum = '6be4f1bb50db879863f4247692360eb830a3eb33'
1008 _build_and_install = ExternalPackage._build_and_install_from_package
1009 _build_and_install_current_dir = (
1010 ExternalPackage._build_and_install_current_dir_noegg)
1011
1012
1013class SixPackage(ExternalPackage):
1014 """six package (dependency for astroid)."""
1015 version = '1.10.0'
1016 url_filename = 'six-%s.tar.gz' % version
1017 local_filename = url_filename
1018 #md5=34eed507548117b2ab523ab14b2f8b55
1019 urls = ('https://pypi.python.org/packages/b3/b2/'
1020 '238e2590826bfdd113244a40d9d3eb26918bd798fc187e2360a8367068db/%s' %
1021 (url_filename),)
1022 hex_sum = '30d480d2e352e8e4c2aae042cf1bf33368ff0920'
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
1028class LruCachePackage(ExternalPackage):
1029 """backports.functools_lru_cache package (dependency for astroid)."""
1030 version = '1.4'
1031 url_filename = 'backports.functools_lru_cache-%s.tar.gz' % version
1032 local_filename = url_filename
1033 #md5=b954e7d5e2ca0f0f66ad2ed12ba800e5
1034 urls = ('https://pypi.python.org/packages/4e/91/'
1035 '0e93d9455254b7b630fb3ebe30cc57cab518660c5fad6a08aac7908a4431/%s' %
1036 (url_filename),)
1037 hex_sum = '8a546e7887e961c2873c9b053f4e2cd2a96bd71d'
Allen Lia9f2a2b2016-08-03 13:44:50 -07001038 _build_and_install = ExternalPackage._build_and_install_from_package
1039 _build_and_install_current_dir = (
1040 ExternalPackage._build_and_install_current_dir_setup_py)
1041
1042
1043class LogilabCommonPackage(ExternalPackage):
1044 """logilab-common package."""
1045 version = '1.2.2'
1046 module_name = 'logilab'
1047 url_filename = 'logilab-common-%s.tar.gz' % version
1048 local_filename = url_filename
1049 #md5=daa7b20c8374ff5f525882cf67e258c0
1050 urls = ('https://pypi.python.org/packages/63/5b/'
1051 'd4d93ad9e683a06354bc5893194514fbf5d05ef86b06b0285762c3724509/%s' %
1052 (url_filename),)
1053 hex_sum = 'ecad2d10c31dcf183c8bed87b6ec35e7ed397d27'
1054 _build_and_install = ExternalPackage._build_and_install_from_package
1055 _build_and_install_current_dir = (
1056 ExternalPackage._build_and_install_current_dir_setup_py)
1057
1058
Dan Shiff5b5ed2016-06-10 10:25:24 -07001059class PyLintPackage(ExternalPackage):
1060 """pylint package."""
Allen Li4c275d22017-07-19 11:56:24 -07001061 version = '1.7.2'
Dan Shiff5b5ed2016-06-10 10:25:24 -07001062 url_filename = 'pylint-%s.tar.gz' % version
1063 local_filename = url_filename
Allen Li4c275d22017-07-19 11:56:24 -07001064 #md5=27ee752cdcfacb05bf4940947e6b35c6
1065 urls = ('https://pypi.python.org/packages/0d/6b/'
1066 '31d4a60fad9f040da6e47a93458f69f41cd9c7367a68b596fa116ca6ed0f/%s' %
Dan Shiff5b5ed2016-06-10 10:25:24 -07001067 (url_filename),)
Allen Li4c275d22017-07-19 11:56:24 -07001068 hex_sum = '42d8b9394e5a485377ae128b01350f25d8b131e0'
1069 _build_and_install = ExternalPackage._build_and_install_from_package
1070 _build_and_install_current_dir = (
1071 ExternalPackage._build_and_install_current_dir_setup_py)
1072
1073
1074class ConfigParserPackage(ExternalPackage):
1075 """configparser package (dependency for pylint)."""
1076 version = '3.5.0'
1077 url_filename = 'configparser-%s.tar.gz' % version
1078 local_filename = url_filename
1079 #md5=cfdd915a5b7a6c09917a64a573140538
1080 urls = ('https://pypi.python.org/packages/7c/69/'
1081 'c2ce7e91c89dc073eb1aa74c0621c3eefbffe8216b3f9af9d3885265c01c/%s' %
1082 (url_filename),)
1083 hex_sum = '8ee6b29c6a11977c0e094da1d4f5f71e7e7ac78b'
1084 _build_and_install = ExternalPackage._build_and_install_from_package
1085 _build_and_install_current_dir = (
1086 ExternalPackage._build_and_install_current_dir_setup_py)
1087
1088
1089class IsortPackage(ExternalPackage):
1090 """isort package (dependency for pylint)."""
1091 version = '4.2.15'
1092 url_filename = 'isort-%s.tar.gz' % version
1093 local_filename = url_filename
1094 #md5=34915a2ce60e6fe3dbcbf5982deef9b4
1095 urls = ('https://pypi.python.org/packages/4d/d5/'
1096 '7c8657126a43bcd3b0173e880407f48be4ac91b4957b51303eab744824cf/%s' %
1097 (url_filename),)
1098 hex_sum = 'acacc36e476b70e13e6fda812c193f4c3c187781'
Dan Shiff5b5ed2016-06-10 10:25:24 -07001099 _build_and_install = ExternalPackage._build_and_install_from_package
1100 _build_and_install_current_dir = (
1101 ExternalPackage._build_and_install_current_dir_setup_py)
1102
1103
Benny Peake4d750422017-03-06 11:53:57 -08001104class Pytz(ExternalPackage):
1105 """Pytz package."""
1106 version = '2016.10'
1107 url_filename = 'pytz-%s.tar.gz' % version
1108 local_filename = url_filename
1109 #md5=cc9f16ba436efabdcef3c4d32ae4919c
1110 urls = ('https://pypi.python.org/packages/42/00/'
1111 '5c89fc6c9b305df84def61863528e899e9dccb196f8438f6cbe960758fc5/%s' %
1112 (url_filename),)
1113 hex_sum = '8d63f1e9b1ee862841b990a7d8ad1d4508d9f0be'
1114 _build_and_install = ExternalPackage._build_and_install_from_package
1115 _build_and_install_current_dir = (
1116 ExternalPackage._build_and_install_current_dir_setup_py)
1117
1118
1119class Tzlocal(ExternalPackage):
1120 """Tzlocal package."""
1121 version = '1.3'
1122 url_filename = 'tzlocal-%s.tar.gz' % version
1123 local_filename = url_filename
1124 # md5=3cb544b3975b59f91a793850a072d4a8
1125 urls = ('https://pypi.python.org/packages/d3/64/'
1126 'e4b18738496213f82b88b31c431a0e4ece143801fb6771dddd1c2bf0101b/%s' %
1127 (url_filename),)
1128 hex_sum = '730e9d7112335865a1dcfabec69c8c3086be424f'
1129 _build_and_install = ExternalPackage._build_and_install_from_package
1130 _build_and_install_current_dir = (
1131 ExternalPackage._build_and_install_current_dir_setup_py)
1132
1133
beepsd9153b52013-01-23 20:52:46 -08001134class _ExternalGitRepo(ExternalPackage):
1135 """
1136 Parent class for any package which needs to pull a git repo.
1137
1138 This class inherits from ExternalPackage only so we can sync git
1139 repos through the build_externals script. We do not reuse any of
1140 ExternalPackage's other methods. Any package that needs a git repo
1141 should subclass this and override build_and_install or fetch as
1142 they see appropriate.
1143 """
1144
Michael Janssena7427612014-11-14 15:44:39 -08001145 os_requirements = {('/usr/bin/git') : 'git-core'}
beepsd9153b52013-01-23 20:52:46 -08001146
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001147 # All the chromiumos projects used on the lab servers should have a 'prod'
1148 # branch used to track the software version deployed in prod.
1149 PROD_BRANCH = 'prod'
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001150 MASTER_BRANCH = 'master'
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001151
beepsd9153b52013-01-23 20:52:46 -08001152 def is_needed(self, unused_install_dir):
1153 """Tell build_externals that we need to fetch."""
1154 # TODO(beeps): check if we're already upto date.
1155 return True
1156
1157
1158 def build_and_install(self, unused_install_dir):
1159 """
1160 Fall through method to install a package.
1161
1162 Overwritten in base classes to pull a git repo.
1163 """
1164 raise NotImplementedError
1165
1166
1167 def fetch(self, unused_dest_dir):
1168 """Fallthrough method to fetch a package."""
1169 return True
1170
1171
1172class HdctoolsRepo(_ExternalGitRepo):
1173 """Clones or updates the hdctools repo."""
1174
Alex Miller0e1217c2013-02-22 10:11:08 -08001175 module_name = 'servo'
beepsd9153b52013-01-23 20:52:46 -08001176 temp_hdctools_dir = tempfile.mktemp(suffix='hdctools')
Alex Miller9fbe67f2013-09-06 15:04:48 -07001177 _GIT_URL = ('https://chromium.googlesource.com/'
beepsd9153b52013-01-23 20:52:46 -08001178 'chromiumos/third_party/hdctools')
1179
1180 def fetch(self, unused_dest_dir):
1181 """
1182 Fetch repo to a temporary location.
1183
1184 We use an intermediate temp directory to stage our
1185 installation because we only care about the servo package.
1186 If we can't get at the top commit hash after fetching
1187 something is wrong. This can happen when we've cloned/pulled
1188 an empty repo. Not something we expect to do.
1189
1190 @parma unused_dest_dir: passed in because we inherit from
1191 ExternalPackage.
1192
1193 @return: True if repo sync was successful.
1194 """
1195 git_repo = revision_control.GitRepo(
1196 self.temp_hdctools_dir,
1197 self._GIT_URL,
1198 None,
beepsaae3f1c2013-03-19 15:49:14 -07001199 abs_work_tree=self.temp_hdctools_dir)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001200 git_repo.reinit_repo_at(self.PROD_BRANCH)
beepsd9153b52013-01-23 20:52:46 -08001201
1202 if git_repo.get_latest_commit_hash():
1203 return True
1204 return False
1205
1206
1207 def build_and_install(self, install_dir):
1208 """Reach into the hdctools repo and rsync only the servo directory."""
1209
1210 servo_dir = os.path.join(self.temp_hdctools_dir, 'servo')
1211 if not os.path.exists(servo_dir):
1212 return False
1213
1214 rv = self._rsync(servo_dir, os.path.join(install_dir, 'servo'))
1215 shutil.rmtree(self.temp_hdctools_dir)
1216 return rv
1217
1218
1219class ChromiteRepo(_ExternalGitRepo):
1220 """Clones or updates the chromite repo."""
1221
Alex Miller9fbe67f2013-09-06 15:04:48 -07001222 _GIT_URL = ('https://chromium.googlesource.com/chromiumos/chromite')
beepsd9153b52013-01-23 20:52:46 -08001223
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001224 def build_and_install(self, install_dir, master_branch=False):
beepsd9153b52013-01-23 20:52:46 -08001225 """
1226 Clone if the repo isn't initialized, pull clean bits if it is.
1227
1228 Unlike it's hdctools counterpart the chromite repo clones master
1229 directly into site-packages. It doesn't use an intermediate temp
Chris Sosa1bf41c42013-02-27 11:34:23 -08001230 directory because it doesn't need installation.
beepsd9153b52013-01-23 20:52:46 -08001231
1232 @param install_dir: destination directory for chromite installation.
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001233 @param master_branch: if True, install master branch. Otherwise,
1234 install prod branch.
beepsd9153b52013-01-23 20:52:46 -08001235 """
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001236 init_branch = (self.MASTER_BRANCH if master_branch
1237 else self.PROD_BRANCH)
beepsd9153b52013-01-23 20:52:46 -08001238 local_chromite_dir = os.path.join(install_dir, 'chromite')
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001239 git_repo = revision_control.GitRepo(
1240 local_chromite_dir,
1241 self._GIT_URL,
1242 abs_work_tree=local_chromite_dir)
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001243 git_repo.reinit_repo_at(init_branch)
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001244
Chris Sosa1bf41c42013-02-27 11:34:23 -08001245
1246 if git_repo.get_latest_commit_hash():
1247 return True
1248 return False
1249
1250
Scott James Remnantbb1a9672014-03-03 14:03:09 -08001251class BtsocketRepo(_ExternalGitRepo):
1252 """Clones or updates the btsocket repo."""
1253
1254 _GIT_URL = ('https://chromium.googlesource.com/'
1255 'chromiumos/platform/btsocket')
1256
1257 def fetch(self, unused_dest_dir):
1258 """
1259 Fetch repo to a temporary location.
1260
1261 We use an intermediate temp directory because we have to build an
1262 egg for installation. If we can't get at the top commit hash after
1263 fetching something is wrong. This can happen when we've cloned/pulled
1264 an empty repo. Not something we expect to do.
1265
1266 @parma unused_dest_dir: passed in because we inherit from
1267 ExternalPackage.
1268
1269 @return: True if repo sync was successful.
1270 """
1271 self.temp_btsocket_dir = autotemp.tempdir(unique_id='btsocket')
1272 try:
1273 git_repo = revision_control.GitRepo(
1274 self.temp_btsocket_dir.name,
1275 self._GIT_URL,
1276 None,
1277 abs_work_tree=self.temp_btsocket_dir.name)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001278 git_repo.reinit_repo_at(self.PROD_BRANCH)
Scott James Remnantbb1a9672014-03-03 14:03:09 -08001279
1280 if git_repo.get_latest_commit_hash():
1281 return True
1282 except:
1283 self.temp_btsocket_dir.clean()
1284 raise
1285
1286 self.temp_btsocket_dir.clean()
1287 return False
1288
1289
1290 def build_and_install(self, install_dir):
1291 """
1292 Install the btsocket module using setup.py
1293
1294 @param install_dir: Target installation directory.
1295
1296 @return: A boolean indicating success of failure.
1297 """
1298 work_dir = os.getcwd()
1299 try:
1300 os.chdir(self.temp_btsocket_dir.name)
1301 rv = self._build_and_install_current_dir_setup_py(install_dir)
1302 finally:
1303 os.chdir(work_dir)
1304 self.temp_btsocket_dir.clean()
J. Richard Barnette428b3442014-07-22 11:38:55 -07001305 return rv