blob: 6821ad5eb1a8c2f914a0846bb9f27337f9408af1 [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
Aviv Keshetaf98d472017-10-03 17:17:56 -070011_CHROMEOS_MIRROR = ('http://commondatastorage.googleapis.com/'
12 'chromeos-mirror/gentoo/distfiles/')
jamesrenff6e5aa2010-02-12 00:46:40 +000013
14
15class Error(Exception):
16 """Local exception to be raised by code in this file."""
17
18class FetchError(Error):
19 """Failed to fetch a package from any of its listed URLs."""
20
21
22def _checksum_file(full_path):
23 """@returns The hex checksum of a file given its pathname."""
24 inputfile = open(full_path, 'rb')
25 try:
26 hex_sum = utils.hash('sha1', inputfile.read()).hexdigest()
27 finally:
28 inputfile.close()
29 return hex_sum
30
31
32def system(commandline):
Dan Shi57631dc2013-02-22 09:54:14 -080033 """Same as os.system(commandline) but logs the command first.
34
35 @param commandline: commandline to be called.
36 """
jamesrenff6e5aa2010-02-12 00:46:40 +000037 logging.info(commandline)
38 return os.system(commandline)
39
40
41def find_top_of_autotest_tree():
42 """@returns The full path to the top of the autotest directory tree."""
43 dirname = os.path.dirname(__file__)
44 autotest_dir = os.path.abspath(os.path.join(dirname, '..'))
45 return autotest_dir
46
47
48class ExternalPackage(object):
49 """
50 Defines an external package with URLs to fetch its sources from and
51 a build_and_install() method to unpack it, build it and install it
52 beneath our own autotest/site-packages directory.
53
54 Base Class. Subclass this to define packages.
beepsd5335852013-04-09 09:58:52 -070055 Note: Unless your subclass has a specific reason to, it should not
56 re-install the package every time build_externals is invoked, as this
57 happens periodically through the scheduler. To avoid doing so the is_needed
58 method needs to return an appropriate value.
jamesrenff6e5aa2010-02-12 00:46:40 +000059
60 Attributes:
61 @attribute urls - A tuple of URLs to try fetching the package from.
62 @attribute local_filename - A local filename to use when saving the
63 fetched package.
Allen Lia9f2a2b2016-08-03 13:44:50 -070064 @attribute dist_name - The name of the Python distribution. For example,
65 the package MySQLdb is included in the distribution named
66 MySQL-python. This is generally the PyPI name. Defaults to the
67 name part of the local_filename.
jamesrenff6e5aa2010-02-12 00:46:40 +000068 @attribute hex_sum - The hex digest (currently SHA1) of this package
69 to be used to verify its contents.
70 @attribute module_name - The installed python module name to be used for
71 for a version check. Defaults to the lower case class name with
72 the word Package stripped off.
xixuand63f4502016-09-12 17:36:55 -070073 @attribute extracted_package_path - The path to package directory after
74 extracting.
jamesrenff6e5aa2010-02-12 00:46:40 +000075 @attribute version - The desired minimum package version.
Michael Janssena7427612014-11-14 15:44:39 -080076 @attribute os_requirements - A dictionary mapping pathname tuples on the
jamesrenff6e5aa2010-02-12 00:46:40 +000077 the OS distribution to a likely name of a package the user
78 needs to install on their system in order to get this file.
Michael Janssena7427612014-11-14 15:44:39 -080079 One of the files in the tuple must exist.
jamesrenff6e5aa2010-02-12 00:46:40 +000080 @attribute name - Read only, the printable name of the package.
81 @attribute subclasses - This class attribute holds a list of all defined
82 subclasses. It is constructed dynamically using the metaclass.
83 """
Dan Shi16c0a502015-07-14 17:29:48 -070084 # Modules that are meant to be installed in system directory, rather than
85 # autotest/site-packages. These modules should be skipped if the module
86 # is already installed in system directory. This prevents an older version
87 # of the module from being installed in system directory.
88 SYSTEM_MODULES = ['setuptools']
89
jamesrenff6e5aa2010-02-12 00:46:40 +000090 subclasses = []
91 urls = ()
92 local_filename = None
Allen Lia9f2a2b2016-08-03 13:44:50 -070093 dist_name = None
jamesrenff6e5aa2010-02-12 00:46:40 +000094 hex_sum = None
95 module_name = None
96 version = None
97 os_requirements = None
98
99
100 class __metaclass__(type):
101 """Any time a subclass is defined, add it to our list."""
102 def __init__(mcs, name, bases, dict):
beepsd9153b52013-01-23 20:52:46 -0800103 if name != 'ExternalPackage' and not name.startswith('_'):
jamesrenff6e5aa2010-02-12 00:46:40 +0000104 mcs.subclasses.append(mcs)
105
106
107 def __init__(self):
108 self.verified_package = ''
109 if not self.module_name:
110 self.module_name = self.name.lower()
Allen Lia9f2a2b2016-08-03 13:44:50 -0700111 if not self.dist_name and self.local_filename:
112 self.dist_name = self.local_filename[:self.local_filename.rindex('-')]
jamesrenff6e5aa2010-02-12 00:46:40 +0000113 self.installed_version = ''
114
115
116 @property
xixuand63f4502016-09-12 17:36:55 -0700117 def extracted_package_path(self):
118 """Return the package path after extracting.
119
120 If the package has assigned its own extracted_package_path, use it.
121 Or use part of its local_filename as the extracting path.
122 """
123 return self.local_filename[:-len(self._get_extension(
124 self.local_filename))]
125
126
127 @property
jamesrenff6e5aa2010-02-12 00:46:40 +0000128 def name(self):
129 """Return the class name with any trailing 'Package' stripped off."""
130 class_name = self.__class__.__name__
131 if class_name.endswith('Package'):
132 return class_name[:-len('Package')]
133 return class_name
134
135
Dan Shi7b6297b2015-06-23 13:54:09 -0700136 def is_needed(self, install_dir):
beepsd5335852013-04-09 09:58:52 -0700137 """
138 Check to see if we need to reinstall a package. This is contingent on:
139 1. Module name: If the name of the module is different from the package,
140 the class that installs it needs to specify a module_name string,
141 so we can try importing the module.
142
143 2. Installed version: If the module doesn't contain a __version__ the
144 class that installs it needs to override the
145 _get_installed_version_from_module method to return an appropriate
146 version string.
147
148 3. Version/Minimum version: The class that installs the package should
149 contain a version string, and an optional minimum version string.
Dan Shi57631dc2013-02-22 09:54:14 -0800150
Dan Shi7b6297b2015-06-23 13:54:09 -0700151 4. install_dir: If the module exists in a different directory, e.g.,
152 /usr/lib/python2.7/dist-packages/, the module will be forced to be
153 installed in install_dir.
154
155 @param install_dir: install directory.
beepsd5335852013-04-09 09:58:52 -0700156 @returns True if self.module_name needs to be built and installed.
Dan Shi57631dc2013-02-22 09:54:14 -0800157 """
jamesrenff6e5aa2010-02-12 00:46:40 +0000158 if not self.module_name or not self.version:
159 logging.warning('version and module_name required for '
160 'is_needed() check to work.')
161 return True
162 try:
163 module = __import__(self.module_name)
164 except ImportError, e:
jamesrenca2a9002010-04-20 22:33:22 +0000165 logging.info("%s isn't present. Will install.", self.module_name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000166 return True
Dan Shi16c0a502015-07-14 17:29:48 -0700167 if (not module.__file__.startswith(install_dir) and
168 not self.module_name in self.SYSTEM_MODULES):
Dan Shi7b6297b2015-06-23 13:54:09 -0700169 logging.info('Module %s is installed in %s, rather than %s. The '
170 'module will be forced to be installed in %s.',
171 self.module_name, module.__file__, install_dir,
172 install_dir)
173 return True
jamesrenff6e5aa2010-02-12 00:46:40 +0000174 self.installed_version = self._get_installed_version_from_module(module)
xixuanebe46312016-09-07 14:45:49 -0700175 if not self.installed_version:
176 return True
177
jamesrenff6e5aa2010-02-12 00:46:40 +0000178 logging.info('imported %s version %s.', self.module_name,
179 self.installed_version)
Dale Curtis74a314b2011-06-23 14:55:46 -0700180 if hasattr(self, 'minimum_version'):
xixuanebe46312016-09-07 14:45:49 -0700181 return LooseVersion(self.minimum_version) > LooseVersion(
182 self.installed_version)
Dale Curtis74a314b2011-06-23 14:55:46 -0700183 else:
xixuanebe46312016-09-07 14:45:49 -0700184 return LooseVersion(self.version) > LooseVersion(
185 self.installed_version)
jamesrenff6e5aa2010-02-12 00:46:40 +0000186
187
188 def _get_installed_version_from_module(self, module):
189 """Ask our module its version string and return it or '' if unknown."""
190 try:
191 return module.__version__
192 except AttributeError:
193 logging.error('could not get version from %s', module)
194 return ''
195
196
197 def _build_and_install(self, install_dir):
198 """Subclasses MUST provide their own implementation."""
199 raise NotImplementedError
200
201
202 def _build_and_install_current_dir(self, install_dir):
203 """
204 Subclasses that use _build_and_install_from_package() MUST provide
205 their own implementation of this method.
206 """
207 raise NotImplementedError
208
209
210 def build_and_install(self, install_dir):
211 """
212 Builds and installs the package. It must have been fetched already.
213
214 @param install_dir - The package installation directory. If it does
215 not exist it will be created.
216 """
217 if not self.verified_package:
218 raise Error('Must call fetch() first. - %s' % self.name)
219 self._check_os_requirements()
220 return self._build_and_install(install_dir)
221
222
223 def _check_os_requirements(self):
224 if not self.os_requirements:
225 return
226 failed = False
Michael Janssena7427612014-11-14 15:44:39 -0800227 for file_names, package_name in self.os_requirements.iteritems():
228 if not any(os.path.exists(file_name) for file_name in file_names):
jamesrenff6e5aa2010-02-12 00:46:40 +0000229 failed = True
Michael Janssena7427612014-11-14 15:44:39 -0800230 logging.error('Can\'t find %s, %s probably needs it.',
J. Richard Barnette6f7606b2015-06-11 17:26:23 -0700231 ' or '.join(file_names), self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000232 logging.error('Perhaps you need to install something similar '
233 'to the %s package for OS first.', package_name)
234 if failed:
235 raise Error('Missing OS requirements for %s. (see above)' %
236 self.name)
237
238
239 def _build_and_install_current_dir_setup_py(self, install_dir):
240 """For use as a _build_and_install_current_dir implementation."""
241 egg_path = self._build_egg_using_setup_py(setup_py='setup.py')
242 if not egg_path:
243 return False
244 return self._install_from_egg(install_dir, egg_path)
245
246
247 def _build_and_install_current_dir_setupegg_py(self, install_dir):
248 """For use as a _build_and_install_current_dir implementation."""
249 egg_path = self._build_egg_using_setup_py(setup_py='setupegg.py')
250 if not egg_path:
251 return False
252 return self._install_from_egg(install_dir, egg_path)
253
254
255 def _build_and_install_current_dir_noegg(self, install_dir):
256 if not self._build_using_setup_py():
257 return False
258 return self._install_using_setup_py_and_rsync(install_dir)
259
260
xixuand63f4502016-09-12 17:36:55 -0700261 def _get_extension(self, package):
262 """Get extension of package."""
263 valid_package_extensions = ['.tar.gz', '.tar.bz2', '.zip']
264 extension = None
265
266 for ext in valid_package_extensions:
267 if package.endswith(ext):
268 extension = ext
269 break
270
271 if not extension:
272 raise Error('Unexpected package file extension on %s' % package)
273
274 return extension
275
276
jamesrenff6e5aa2010-02-12 00:46:40 +0000277 def _build_and_install_from_package(self, install_dir):
278 """
279 This method may be used as a _build_and_install() implementation
280 for subclasses if they implement _build_and_install_current_dir().
281
282 Extracts the .tar.gz file, chdirs into the extracted directory
283 (which is assumed to match the tar filename) and calls
284 _build_and_isntall_current_dir from there.
285
286 Afterwards the build (regardless of failure) extracted .tar.gz
287 directory is cleaned up.
288
289 @returns True on success, False otherwise.
290
291 @raises OSError If the expected extraction directory does not exist.
292 """
293 self._extract_compressed_package()
xixuand63f4502016-09-12 17:36:55 -0700294 extension = self._get_extension(self.verified_package)
jamesrenff6e5aa2010-02-12 00:46:40 +0000295 os.chdir(os.path.dirname(self.verified_package))
xixuand63f4502016-09-12 17:36:55 -0700296 os.chdir(self.extracted_package_path)
jamesrenff6e5aa2010-02-12 00:46:40 +0000297 extracted_dir = os.getcwd()
298 try:
299 return self._build_and_install_current_dir(install_dir)
300 finally:
301 os.chdir(os.path.join(extracted_dir, '..'))
302 shutil.rmtree(extracted_dir)
303
304
305 def _extract_compressed_package(self):
306 """Extract the fetched compressed .tar or .zip within its directory."""
307 if not self.verified_package:
308 raise Error('Package must have been fetched first.')
309 os.chdir(os.path.dirname(self.verified_package))
310 if self.verified_package.endswith('gz'):
311 status = system("tar -xzf '%s'" % self.verified_package)
312 elif self.verified_package.endswith('bz2'):
313 status = system("tar -xjf '%s'" % self.verified_package)
314 elif self.verified_package.endswith('zip'):
315 status = system("unzip '%s'" % self.verified_package)
316 else:
317 raise Error('Unknown compression suffix on %s.' %
318 self.verified_package)
319 if status:
320 raise Error('tar failed with %s' % (status,))
321
322
323 def _build_using_setup_py(self, setup_py='setup.py'):
324 """
325 Assuming the cwd is the extracted python package, execute a simple
326 python setup.py build.
327
328 @param setup_py - The name of the setup.py file to execute.
329
330 @returns True on success, False otherwise.
331 """
332 if not os.path.exists(setup_py):
333 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
334 status = system("'%s' %s build" % (sys.executable, setup_py))
335 if status:
Dan Shi57631dc2013-02-22 09:54:14 -0800336 logging.error('%s build failed.', self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000337 return False
338 return True
339
340
341 def _build_egg_using_setup_py(self, setup_py='setup.py'):
342 """
343 Assuming the cwd is the extracted python package, execute a simple
344 python setup.py bdist_egg.
345
346 @param setup_py - The name of the setup.py file to execute.
347
348 @returns The relative path to the resulting egg file or '' on failure.
349 """
350 if not os.path.exists(setup_py):
351 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
352 egg_subdir = 'dist'
353 if os.path.isdir(egg_subdir):
354 shutil.rmtree(egg_subdir)
355 status = system("'%s' %s bdist_egg" % (sys.executable, setup_py))
356 if status:
357 logging.error('bdist_egg of setuptools failed.')
358 return ''
359 # I've never seen a bdist_egg lay multiple .egg files.
360 for filename in os.listdir(egg_subdir):
361 if filename.endswith('.egg'):
362 return os.path.join(egg_subdir, filename)
363
364
365 def _install_from_egg(self, install_dir, egg_path):
366 """
367 Install a module from an egg file by unzipping the necessary parts
368 into install_dir.
369
370 @param install_dir - The installation directory.
371 @param egg_path - The pathname of the egg file.
372 """
373 status = system("unzip -q -o -d '%s' '%s'" % (install_dir, egg_path))
374 if status:
375 logging.error('unzip of %s failed', egg_path)
376 return False
Allen Lia9f2a2b2016-08-03 13:44:50 -0700377 egg_info_dir = os.path.join(install_dir, 'EGG-INFO')
378 if os.path.isdir(egg_info_dir):
379 egg_info_new_path = self._get_egg_info_path(install_dir)
380 if egg_info_new_path:
381 if os.path.exists(egg_info_new_path):
382 shutil.rmtree(egg_info_new_path)
383 os.rename(egg_info_dir, egg_info_new_path)
384 else:
385 shutil.rmtree(egg_info_dir)
jamesrenff6e5aa2010-02-12 00:46:40 +0000386 return True
387
388
Allen Lia9f2a2b2016-08-03 13:44:50 -0700389 def _get_egg_info_path(self, install_dir):
390 """Get egg-info path for this package.
391
392 Example path: install_dir/MySQL_python-1.2.3.egg-info
393
394 """
395 if self.dist_name:
396 egg_info_name_part = self.dist_name.replace('-', '_')
397 if self.version:
398 egg_info_filename = '%s-%s.egg-info' % (egg_info_name_part,
399 self.version)
400 else:
401 egg_info_filename = '%s.egg-info' % (egg_info_name_part,)
402 return os.path.join(install_dir, egg_info_filename)
403 else:
404 return None
405
406
jamesrenff6e5aa2010-02-12 00:46:40 +0000407 def _get_temp_dir(self):
408 return tempfile.mkdtemp(dir='/var/tmp')
409
410
411 def _site_packages_path(self, temp_dir):
412 # This makes assumptions about what python setup.py install
413 # does when given a prefix. Is this always correct?
414 python_xy = 'python%s' % sys.version[:3]
415 return os.path.join(temp_dir, 'lib', python_xy, 'site-packages')
416
417
beepsd9153b52013-01-23 20:52:46 -0800418 def _rsync (self, temp_site_dir, install_dir):
419 """Rsync contents. """
420 status = system("rsync -r '%s/' '%s/'" %
421 (os.path.normpath(temp_site_dir),
422 os.path.normpath(install_dir)))
423 if status:
424 logging.error('%s rsync to install_dir failed.', self.name)
425 return False
426 return True
427
428
jamesrenff6e5aa2010-02-12 00:46:40 +0000429 def _install_using_setup_py_and_rsync(self, install_dir,
430 setup_py='setup.py',
431 temp_dir=None):
432 """
433 Assuming the cwd is the extracted python package, execute a simple:
434
435 python setup.py install --prefix=BLA
436
437 BLA will be a temporary directory that everything installed will
438 be picked out of and rsynced to the appropriate place under
439 install_dir afterwards.
440
441 Afterwards, it deconstructs the extra lib/pythonX.Y/site-packages/
442 directory tree that setuptools created and moves all installed
443 site-packages directly up into install_dir itself.
444
445 @param install_dir the directory for the install to happen under.
446 @param setup_py - The name of the setup.py file to execute.
447
448 @returns True on success, False otherwise.
449 """
450 if not os.path.exists(setup_py):
451 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
452
453 if temp_dir is None:
454 temp_dir = self._get_temp_dir()
455
456 try:
457 status = system("'%s' %s install --no-compile --prefix='%s'"
458 % (sys.executable, setup_py, temp_dir))
459 if status:
Dan Shi57631dc2013-02-22 09:54:14 -0800460 logging.error('%s install failed.', self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000461 return False
462
463 if os.path.isdir(os.path.join(temp_dir, 'lib')):
464 # NOTE: This ignores anything outside of the lib/ dir that
465 # was installed.
466 temp_site_dir = self._site_packages_path(temp_dir)
467 else:
468 temp_site_dir = temp_dir
469
beepsd9153b52013-01-23 20:52:46 -0800470 return self._rsync(temp_site_dir, install_dir)
jamesrenff6e5aa2010-02-12 00:46:40 +0000471 finally:
472 shutil.rmtree(temp_dir)
473
474
475
476 def _build_using_make(self, install_dir):
477 """Build the current package using configure/make.
478
479 @returns True on success, False otherwise.
480 """
481 install_prefix = os.path.join(install_dir, 'usr', 'local')
482 status = system('./configure --prefix=%s' % install_prefix)
483 if status:
484 logging.error('./configure failed for %s', self.name)
485 return False
486 status = system('make')
487 if status:
488 logging.error('make failed for %s', self.name)
489 return False
490 status = system('make check')
491 if status:
492 logging.error('make check failed for %s', self.name)
493 return False
494 return True
495
496
497 def _install_using_make(self):
498 """Install the current package using make install.
499
500 Assumes the install path was set up while running ./configure (in
501 _build_using_make()).
502
503 @returns True on success, False otherwise.
504 """
505 status = system('make install')
506 return status == 0
507
508
509 def fetch(self, dest_dir):
510 """
511 Fetch the package from one its URLs and save it in dest_dir.
512
513 If the the package already exists in dest_dir and the checksum
514 matches this code will not fetch it again.
515
516 Sets the 'verified_package' attribute with the destination pathname.
517
518 @param dest_dir - The destination directory to save the local file.
519 If it does not exist it will be created.
520
521 @returns A boolean indicating if we the package is now in dest_dir.
522 @raises FetchError - When something unexpected happens.
523 """
524 if not os.path.exists(dest_dir):
525 os.makedirs(dest_dir)
526 local_path = os.path.join(dest_dir, self.local_filename)
527
528 # If the package exists, verify its checksum and be happy if it is good.
529 if os.path.exists(local_path):
530 actual_hex_sum = _checksum_file(local_path)
531 if self.hex_sum == actual_hex_sum:
532 logging.info('Good checksum for existing %s package.',
533 self.name)
534 self.verified_package = local_path
535 return True
536 logging.warning('Bad checksum for existing %s package. '
537 'Re-downloading', self.name)
538 os.rename(local_path, local_path + '.wrong-checksum')
539
540 # Download the package from one of its urls, rejecting any if the
541 # checksum does not match.
542 for url in self.urls:
543 logging.info('Fetching %s', url)
544 try:
545 url_file = urllib2.urlopen(url)
546 except (urllib2.URLError, EnvironmentError):
547 logging.warning('Could not fetch %s package from %s.',
548 self.name, url)
549 continue
Dan Shi57631dc2013-02-22 09:54:14 -0800550
jamesrenff6e5aa2010-02-12 00:46:40 +0000551 data_length = int(url_file.info().get('Content-Length',
552 _MAX_PACKAGE_SIZE))
553 if data_length <= 0 or data_length > _MAX_PACKAGE_SIZE:
554 raise FetchError('%s from %s fails Content-Length %d '
555 'sanity check.' % (self.name, url,
556 data_length))
557 checksum = utils.hash('sha1')
558 total_read = 0
559 output = open(local_path, 'wb')
560 try:
561 while total_read < data_length:
562 data = url_file.read(_READ_SIZE)
563 if not data:
564 break
565 output.write(data)
566 checksum.update(data)
567 total_read += len(data)
568 finally:
569 output.close()
570 if self.hex_sum != checksum.hexdigest():
571 logging.warning('Bad checksum for %s fetched from %s.',
572 self.name, url)
573 logging.warning('Got %s', checksum.hexdigest())
574 logging.warning('Expected %s', self.hex_sum)
575 os.unlink(local_path)
576 continue
577 logging.info('Good checksum.')
578 self.verified_package = local_path
579 return True
580 else:
581 return False
582
583
584# NOTE: This class definition must come -before- all other ExternalPackage
585# classes that need to use this version of setuptools so that is is inserted
586# into the ExternalPackage.subclasses list before them.
587class SetuptoolsPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800588 """setuptools package"""
jamesrenff6e5aa2010-02-12 00:46:40 +0000589 # For all known setuptools releases a string compare works for the
590 # version string. Hopefully they never release a 0.10. (Their own
591 # version comparison code would break if they did.)
Dan Shi16c0a502015-07-14 17:29:48 -0700592 # Any system with setuptools > 18.0.1 is fine. If none installed, then
Dale Curtis74a314b2011-06-23 14:55:46 -0700593 # try to install the latest found on the upstream.
Dan Shi16c0a502015-07-14 17:29:48 -0700594 minimum_version = '18.0.1'
595 version = '18.0.1'
Aviv Keshetaf98d472017-10-03 17:17:56 -0700596 urls = (_CHROMEOS_MIRROR + 'setuptools-%s.tar.gz' % (version,),)
jamesrenff6e5aa2010-02-12 00:46:40 +0000597 local_filename = 'setuptools-%s.tar.gz' % version
Dan Shi16c0a502015-07-14 17:29:48 -0700598 hex_sum = 'ebc4fe81b7f6d61d923d9519f589903824044f52'
jamesrenff6e5aa2010-02-12 00:46:40 +0000599
600 SUDO_SLEEP_DELAY = 15
601
602
603 def _build_and_install(self, install_dir):
604 """Install setuptools on the system."""
605 logging.info('NOTE: setuptools install does not use install_dir.')
606 return self._build_and_install_from_package(install_dir)
607
608
609 def _build_and_install_current_dir(self, install_dir):
610 egg_path = self._build_egg_using_setup_py()
611 if not egg_path:
612 return False
613
614 print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n'
615 print 'About to run sudo to install setuptools', self.version
616 print 'on your system for use by', sys.executable, '\n'
617 print '!! ^C within', self.SUDO_SLEEP_DELAY, 'seconds to abort.\n'
618 time.sleep(self.SUDO_SLEEP_DELAY)
619
620 # Copy the egg to the local filesystem /var/tmp so that root can
621 # access it properly (avoid NFS squashroot issues).
622 temp_dir = self._get_temp_dir()
623 try:
624 shutil.copy(egg_path, temp_dir)
625 egg_name = os.path.split(egg_path)[1]
626 temp_egg = os.path.join(temp_dir, egg_name)
627 p = subprocess.Popen(['sudo', '/bin/sh', temp_egg],
628 stdout=subprocess.PIPE)
629 regex = re.compile('Copying (.*?) to (.*?)\n')
630 match = regex.search(p.communicate()[0])
631 status = p.wait()
632
633 if match:
634 compiled = os.path.join(match.group(2), match.group(1))
635 os.system("sudo chmod a+r '%s'" % compiled)
636 finally:
637 shutil.rmtree(temp_dir)
638
639 if status:
640 logging.error('install of setuptools from egg failed.')
641 return False
642 return True
643
644
645class MySQLdbPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800646 """mysql package, used in scheduler."""
jamesrenff6e5aa2010-02-12 00:46:40 +0000647 module_name = 'MySQLdb'
Alex Miller47d61282013-04-17 13:53:58 -0700648 version = '1.2.3'
jamesrenff6e5aa2010-02-12 00:46:40 +0000649 local_filename = 'MySQL-python-%s.tar.gz' % version
Aviv Keshet34fad2f2017-09-29 14:06:03 -0700650 urls = ('http://commondatastorage.googleapis.com/chromeos-mirror/gentoo/'
651 'distfiles/%s' % local_filename,)
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'):
Allen Li74e4b282018-01-19 14:03:45 -0800660 error_msg = '''\
661You need to install /usr/bin/mysql_config.
662On recent Debian based distros, run: \
663sudo apt-get install libmariadbclient-dev-compat
664On older Debian based distros, run: sudo apt-get install libmysqlclient15-dev
665'''
Dan Shi16c0a502015-07-14 17:29:48 -0700666 logging.error(error_msg)
667 return False, error_msg
jamesrenff6e5aa2010-02-12 00:46:40 +0000668 return self._build_and_install_from_package(install_dir)
669
670
671class DjangoPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800672 """django package."""
Alex Miller47d61282013-04-17 13:53:58 -0700673 version = '1.5.1'
jamesrenff6e5aa2010-02-12 00:46:40 +0000674 local_filename = 'Django-%s.tar.gz' % version
Aviv Keshetaf98d472017-10-03 17:17:56 -0700675 urls = (_CHROMEOS_MIRROR + local_filename,)
Alex Miller47d61282013-04-17 13:53:58 -0700676 hex_sum = '0ab97b90c4c79636e56337f426f1e875faccbba1'
jamesrenff6e5aa2010-02-12 00:46:40 +0000677
678 _build_and_install = ExternalPackage._build_and_install_from_package
679 _build_and_install_current_dir = (
680 ExternalPackage._build_and_install_current_dir_noegg)
681
682
683 def _get_installed_version_from_module(self, module):
684 try:
685 return module.get_version().split()[0]
686 except AttributeError:
687 return '0.9.6'
688
689
690
691class NumpyPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800692 """numpy package, required by matploglib."""
Scott Zawalskic2c9a012013-03-06 09:13:26 -0500693 version = '1.7.0'
jamesrenff6e5aa2010-02-12 00:46:40 +0000694 local_filename = 'numpy-%s.tar.gz' % version
Aviv Keshetaf98d472017-10-03 17:17:56 -0700695 urls = (_CHROMEOS_MIRROR + local_filename,)
Scott Zawalskic2c9a012013-03-06 09:13:26 -0500696 hex_sum = 'ba328985f20390b0f969a5be2a6e1141d5752cf9'
jamesrenff6e5aa2010-02-12 00:46:40 +0000697
698 _build_and_install = ExternalPackage._build_and_install_from_package
699 _build_and_install_current_dir = (
700 ExternalPackage._build_and_install_current_dir_setupegg_py)
701
702
jamesrenff6e5aa2010-02-12 00:46:40 +0000703
beeps32a63082013-08-22 14:02:29 -0700704class JsonRPCLib(ExternalPackage):
705 """jsonrpclib package"""
706 version = '0.1.3'
707 module_name = 'jsonrpclib'
708 local_filename = '%s-%s.tar.gz' % (module_name, version)
Aviv Keshetaf98d472017-10-03 17:17:56 -0700709 urls = (_CHROMEOS_MIRROR + local_filename,)
beeps32a63082013-08-22 14:02:29 -0700710 hex_sum = '431714ed19ab677f641ce5d678a6a95016f5c452'
711
712 def _get_installed_version_from_module(self, module):
713 # jsonrpclib doesn't contain a proper version
714 return self.version
715
716 _build_and_install = ExternalPackage._build_and_install_from_package
717 _build_and_install_current_dir = (
718 ExternalPackage._build_and_install_current_dir_noegg)
719
720
jamesrenff6e5aa2010-02-12 00:46:40 +0000721class GwtPackage(ExternalPackage):
722 """Fetch and extract a local copy of GWT used to build the frontend."""
723
Dale Curtis74a314b2011-06-23 14:55:46 -0700724 version = '2.3.0'
jamesren012d0322010-04-30 20:21:29 +0000725 local_filename = 'gwt-%s.zip' % version
Aviv Keshetdeb7a942017-11-05 17:58:45 -0800726 urls = (_CHROMEOS_MIRROR + local_filename,)
Dale Curtis74a314b2011-06-23 14:55:46 -0700727 hex_sum = 'd51fce9166e6b31349659ffca89baf93e39bc84b'
jamesrenff6e5aa2010-02-12 00:46:40 +0000728 name = 'gwt'
729 about_filename = 'about.txt'
730 module_name = None # Not a Python module.
731
732
733 def is_needed(self, install_dir):
734 gwt_dir = os.path.join(install_dir, self.name)
735 about_file = os.path.join(install_dir, self.name, self.about_filename)
736
737 if not os.path.exists(gwt_dir) or not os.path.exists(about_file):
738 logging.info('gwt not installed for autotest')
739 return True
740
741 f = open(about_file, 'r')
742 version_line = f.readline()
743 f.close()
744
745 match = re.match(r'Google Web Toolkit (.*)', version_line)
746 if not match:
747 logging.info('did not find gwt version')
748 return True
749
750 logging.info('found gwt version %s', match.group(1))
751 return match.group(1) != self.version
752
753
jamesrenca2a9002010-04-20 22:33:22 +0000754 def _build_and_install(self, install_dir):
jamesrenff6e5aa2010-02-12 00:46:40 +0000755 os.chdir(install_dir)
756 self._extract_compressed_package()
jamesren012d0322010-04-30 20:21:29 +0000757 extracted_dir = self.local_filename[:-len('.zip')]
jamesrenff6e5aa2010-02-12 00:46:40 +0000758 target_dir = os.path.join(install_dir, self.name)
759 if os.path.exists(target_dir):
760 shutil.rmtree(target_dir)
761 os.rename(extracted_dir, target_dir)
762 return True
763
764
Alex Millere7b6e8b2013-02-13 17:34:04 -0800765class PyudevPackage(ExternalPackage):
766 """
767 pyudev module
768
769 Used in unittests.
770 """
771 version = '0.16.1'
772 url_filename = 'pyudev-%s.tar.gz' % version
773 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700774 urls = (_CHROMEOS_MIRROR + local_filename,)
Alex Millere7b6e8b2013-02-13 17:34:04 -0800775 hex_sum = 'b36bc5c553ce9b56d32a5e45063a2c88156771c0'
776
777 _build_and_install = ExternalPackage._build_and_install_from_package
778 _build_and_install_current_dir = (
779 ExternalPackage._build_and_install_current_dir_setup_py)
780
781
782class PyMoxPackage(ExternalPackage):
783 """
784 mox module
785
786 Used in unittests.
787 """
788 module_name = 'mox'
789 version = '0.5.3'
Aviv Keshet96ccfda2017-11-03 12:47:25 -0700790 # Note: url_filename does not match local_filename, because of
791 # an uncontrolled fork at some point in time of mox versions.
792 url_filename = 'mox-%s-autotest.tar.gz' % version
793 local_filename = 'mox-%s.tar.gz' % version
794 urls = (_CHROMEOS_MIRROR + url_filename,)
Alex Millere7b6e8b2013-02-13 17:34:04 -0800795 hex_sum = '1c502d2c0a8aefbba2c7f385a83d33e7d822452a'
796
797 _build_and_install = ExternalPackage._build_and_install_from_package
798 _build_and_install_current_dir = (
799 ExternalPackage._build_and_install_current_dir_noegg)
800
801 def _get_installed_version_from_module(self, module):
802 # mox doesn't contain a proper version
803 return self.version
804
805
Jason Abele2371d1a2013-11-22 12:18:18 -0800806class PySeleniumPackage(ExternalPackage):
807 """
808 selenium module
809
810 Used in wifi_interop suite.
811 """
812 module_name = 'selenium'
813 version = '2.37.2'
814 url_filename = 'selenium-%s.tar.gz' % version
815 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700816 urls = (_CHROMEOS_MIRROR + local_filename,)
Jason Abele2371d1a2013-11-22 12:18:18 -0800817 hex_sum = '66946d5349e36d946daaad625c83c30c11609e36'
818
819 _build_and_install = ExternalPackage._build_and_install_from_package
820 _build_and_install_current_dir = (
821 ExternalPackage._build_and_install_current_dir_setup_py)
822
823
Simran Basid6b83772014-01-06 16:31:30 -0800824class FaultHandlerPackage(ExternalPackage):
825 """
826 faulthandler module
827 """
828 module_name = 'faulthandler'
829 version = '2.3'
830 url_filename = '%s-%s.tar.gz' % (module_name, version)
831 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700832 urls = (_CHROMEOS_MIRROR + local_filename,)
Simran Basid6b83772014-01-06 16:31:30 -0800833 hex_sum = 'efb30c068414fba9df892e48fcf86170cbf53589'
834
835 _build_and_install = ExternalPackage._build_and_install_from_package
836 _build_and_install_current_dir = (
837 ExternalPackage._build_and_install_current_dir_noegg)
838
839
Alex Millera87e5482014-06-09 16:04:49 -0700840class PsutilPackage(ExternalPackage):
841 """
842 psutil module
843 """
844 module_name = 'psutil'
845 version = '2.1.1'
846 url_filename = '%s-%s.tar.gz' % (module_name, version)
847 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700848 urls = (_CHROMEOS_MIRROR + local_filename,)
Alex Millera87e5482014-06-09 16:04:49 -0700849 hex_sum = '0c20a20ed316e69f2b0881530439213988229916'
850
851 _build_and_install = ExternalPackage._build_and_install_from_package
852 _build_and_install_current_dir = (
853 ExternalPackage._build_and_install_current_dir_setup_py)
854
855
Michael Liangd2d294c2014-06-24 15:24:49 -0700856class ElasticSearchPackage(ExternalPackage):
857 """elasticsearch-py package."""
Dan Shicae83c72015-07-22 17:58:21 -0700858 version = '1.6.0'
Michael Liangd2d294c2014-06-24 15:24:49 -0700859 url_filename = 'elasticsearch-%s.tar.gz' % version
860 local_filename = url_filename
Michael Liang5934b712014-08-12 15:45:14 -0700861 urls = ('https://pypi.python.org/packages/source/e/elasticsearch/%s' %
862 (url_filename),)
Dan Shicae83c72015-07-22 17:58:21 -0700863 hex_sum = '3e676c96f47935b1f52df82df3969564bd356b1c'
Michael Liangd2d294c2014-06-24 15:24:49 -0700864 _build_and_install = ExternalPackage._build_and_install_from_package
865 _build_and_install_current_dir = (
866 ExternalPackage._build_and_install_current_dir_setup_py)
867
xixuanebe46312016-09-07 14:45:49 -0700868 def _get_installed_version_from_module(self, module):
869 # Elastic's version format is like tuple (1, 6, 0), which needs to be
870 # transferred to 1.6.0.
871 try:
872 return '.'.join(str(i) for i in module.__version__)
873 except:
874 return self.version
875
Michael Liangd2d294c2014-06-24 15:24:49 -0700876
Michael Liang5934b712014-08-12 15:45:14 -0700877class Urllib3Package(ExternalPackage):
878 """elasticsearch-py package."""
879 version = '1.9'
880 url_filename = 'urllib3-%s.tar.gz' % version
881 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700882 urls = (_CHROMEOS_MIRROR + local_filename,)
Michael Liang5934b712014-08-12 15:45:14 -0700883 hex_sum = '9522197efb2a2b49ce804de3a515f06d97b6602f'
884 _build_and_install = ExternalPackage._build_and_install_from_package
885 _build_and_install_current_dir = (
886 ExternalPackage._build_and_install_current_dir_setup_py)
887
Wai-Hong Tam8ce47ea2017-05-12 11:33:43 -0700888class ImagingLibraryPackage(ExternalPackage):
889 """Python Imaging Library (PIL)."""
890 version = '1.1.7'
891 url_filename = 'Imaging-%s.tar.gz' % version
892 local_filename = url_filename
893 urls = ('http://commondatastorage.googleapis.com/chromeos-mirror/gentoo/'
894 'distfiles/%s' % url_filename,)
895 hex_sum = '76c37504251171fda8da8e63ecb8bc42a69a5c81'
896
897 def _build_and_install(self, install_dir):
898 #The path of zlib library might be different from what PIL setup.py is
899 #expected. Following change does the best attempt to link the library
900 #to a path PIL setup.py will try.
901 libz_possible_path = '/usr/lib/x86_64-linux-gnu/libz.so'
902 libz_expected_path = '/usr/lib/libz.so'
903 if (os.path.exists(libz_possible_path) and
904 not os.path.exists(libz_expected_path)):
905 utils.run('sudo ln -s %s %s' %
906 (libz_possible_path, libz_expected_path))
907 return self._build_and_install_from_package(install_dir)
908
909 _build_and_install_current_dir = (
910 ExternalPackage._build_and_install_current_dir_noegg)
J. Richard Barnette428b3442014-07-22 11:38:55 -0700911
912
Allen Lia9f2a2b2016-08-03 13:44:50 -0700913class AstroidPackage(ExternalPackage):
914 """astroid package."""
Allen Li4c275d22017-07-19 11:56:24 -0700915 version = '1.5.3'
Allen Lia9f2a2b2016-08-03 13:44:50 -0700916 url_filename = 'astroid-%s.tar.gz' % version
917 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700918 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -0700919 hex_sum = 'e654225ab5bd2788e5e246b156910990bf33cde6'
920 _build_and_install = ExternalPackage._build_and_install_from_package
921 _build_and_install_current_dir = (
922 ExternalPackage._build_and_install_current_dir_setup_py)
923
924
925class LazyObjectProxyPackage(ExternalPackage):
926 """lazy-object-proxy package (dependency for astroid)."""
927 version = '1.3.1'
928 url_filename = 'lazy-object-proxy-%s.tar.gz' % version
929 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700930 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -0700931 hex_sum = '984828d8f672986ca926373986214d7057b772fb'
932 _build_and_install = ExternalPackage._build_and_install_from_package
933 _build_and_install_current_dir = (
934 ExternalPackage._build_and_install_current_dir_setup_py)
935
936
937class SingleDispatchPackage(ExternalPackage):
938 """singledispatch package (dependency for astroid)."""
939 version = '3.4.0.3'
940 url_filename = 'singledispatch-%s.tar.gz' % version
941 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700942 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -0700943 hex_sum = 'f93241b06754a612af8bb7aa208c4d1805637022'
944 _build_and_install = ExternalPackage._build_and_install_from_package
945 _build_and_install_current_dir = (
946 ExternalPackage._build_and_install_current_dir_setup_py)
947
948
949class Enum34Package(ExternalPackage):
950 """enum34 package (dependency for astroid)."""
951 version = '1.1.6'
952 url_filename = 'enum34-%s.tar.gz' % version
953 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700954 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -0700955 hex_sum = '014ef5878333ff91099893d615192c8cd0b1525a'
956 _build_and_install = ExternalPackage._build_and_install_from_package
957 _build_and_install_current_dir = (
958 ExternalPackage._build_and_install_current_dir_setup_py)
959
960
961class WraptPackage(ExternalPackage):
962 """wrapt package (dependency for astroid)."""
963 version = '1.10.10'
964 url_filename = 'wrapt-%s.tar.gz' % version
965 local_filename = url_filename
966 #md5=97365e906afa8b431f266866ec4e2e18
967 urls = ('https://pypi.python.org/packages/a3/bb/'
968 '525e9de0a220060394f4aa34fdf6200853581803d92714ae41fc3556e7d7/%s' %
969 (url_filename),)
970 hex_sum = '6be4f1bb50db879863f4247692360eb830a3eb33'
971 _build_and_install = ExternalPackage._build_and_install_from_package
972 _build_and_install_current_dir = (
973 ExternalPackage._build_and_install_current_dir_noegg)
974
975
976class SixPackage(ExternalPackage):
977 """six package (dependency for astroid)."""
978 version = '1.10.0'
979 url_filename = 'six-%s.tar.gz' % version
980 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700981 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -0700982 hex_sum = '30d480d2e352e8e4c2aae042cf1bf33368ff0920'
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
988class LruCachePackage(ExternalPackage):
989 """backports.functools_lru_cache package (dependency for astroid)."""
990 version = '1.4'
991 url_filename = 'backports.functools_lru_cache-%s.tar.gz' % version
992 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700993 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -0700994 hex_sum = '8a546e7887e961c2873c9b053f4e2cd2a96bd71d'
Allen Lia9f2a2b2016-08-03 13:44:50 -0700995 _build_and_install = ExternalPackage._build_and_install_from_package
996 _build_and_install_current_dir = (
997 ExternalPackage._build_and_install_current_dir_setup_py)
998
999
1000class LogilabCommonPackage(ExternalPackage):
1001 """logilab-common package."""
1002 version = '1.2.2'
1003 module_name = 'logilab'
1004 url_filename = 'logilab-common-%s.tar.gz' % version
1005 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -07001006 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Lia9f2a2b2016-08-03 13:44:50 -07001007 hex_sum = 'ecad2d10c31dcf183c8bed87b6ec35e7ed397d27'
1008 _build_and_install = ExternalPackage._build_and_install_from_package
1009 _build_and_install_current_dir = (
1010 ExternalPackage._build_and_install_current_dir_setup_py)
1011
1012
Dan Shiff5b5ed2016-06-10 10:25:24 -07001013class PyLintPackage(ExternalPackage):
1014 """pylint package."""
Allen Li4c275d22017-07-19 11:56:24 -07001015 version = '1.7.2'
Dan Shiff5b5ed2016-06-10 10:25:24 -07001016 url_filename = 'pylint-%s.tar.gz' % version
1017 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -07001018 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -07001019 hex_sum = '42d8b9394e5a485377ae128b01350f25d8b131e0'
1020 _build_and_install = ExternalPackage._build_and_install_from_package
1021 _build_and_install_current_dir = (
1022 ExternalPackage._build_and_install_current_dir_setup_py)
1023
1024
1025class ConfigParserPackage(ExternalPackage):
1026 """configparser package (dependency for pylint)."""
1027 version = '3.5.0'
1028 url_filename = 'configparser-%s.tar.gz' % version
1029 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -07001030 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -07001031 hex_sum = '8ee6b29c6a11977c0e094da1d4f5f71e7e7ac78b'
1032 _build_and_install = ExternalPackage._build_and_install_from_package
1033 _build_and_install_current_dir = (
1034 ExternalPackage._build_and_install_current_dir_setup_py)
1035
1036
1037class IsortPackage(ExternalPackage):
1038 """isort package (dependency for pylint)."""
1039 version = '4.2.15'
1040 url_filename = 'isort-%s.tar.gz' % version
1041 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -07001042 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -07001043 hex_sum = 'acacc36e476b70e13e6fda812c193f4c3c187781'
Dan Shiff5b5ed2016-06-10 10:25:24 -07001044 _build_and_install = ExternalPackage._build_and_install_from_package
1045 _build_and_install_current_dir = (
1046 ExternalPackage._build_and_install_current_dir_setup_py)
1047
1048
Daniel Erata749a792018-03-26 12:28:17 -07001049class DateutilPackage(ExternalPackage):
1050 """python-dateutil package."""
1051 version = '2.6.1'
1052 local_filename = 'python-dateutil-%s.tar.gz' % version
1053 urls = (_CHROMEOS_MIRROR + local_filename,)
1054 hex_sum = 'db2ace298dee7e47fd720ed03eb790885347bf4e'
1055
1056 _build_and_install = ExternalPackage._build_and_install_from_package
1057 _build_and_install_current_dir = (
1058 ExternalPackage._build_and_install_current_dir_setup_py)
1059
1060
Benny Peake4d750422017-03-06 11:53:57 -08001061class Pytz(ExternalPackage):
1062 """Pytz package."""
1063 version = '2016.10'
1064 url_filename = 'pytz-%s.tar.gz' % version
1065 local_filename = url_filename
1066 #md5=cc9f16ba436efabdcef3c4d32ae4919c
1067 urls = ('https://pypi.python.org/packages/42/00/'
1068 '5c89fc6c9b305df84def61863528e899e9dccb196f8438f6cbe960758fc5/%s' %
1069 (url_filename),)
1070 hex_sum = '8d63f1e9b1ee862841b990a7d8ad1d4508d9f0be'
1071 _build_and_install = ExternalPackage._build_and_install_from_package
1072 _build_and_install_current_dir = (
1073 ExternalPackage._build_and_install_current_dir_setup_py)
1074
1075
1076class Tzlocal(ExternalPackage):
1077 """Tzlocal package."""
1078 version = '1.3'
1079 url_filename = 'tzlocal-%s.tar.gz' % version
1080 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -07001081 urls = (_CHROMEOS_MIRROR + local_filename,)
Benny Peake4d750422017-03-06 11:53:57 -08001082 hex_sum = '730e9d7112335865a1dcfabec69c8c3086be424f'
1083 _build_and_install = ExternalPackage._build_and_install_from_package
1084 _build_and_install_current_dir = (
1085 ExternalPackage._build_and_install_current_dir_setup_py)
1086
1087
Rohit Makasanab822a0c2017-11-29 15:35:17 -08001088class PyYAMLPackage(ExternalPackage):
1089 """pyyaml package."""
1090 version = '3.12'
1091 local_filename = 'PyYAML-%s.tar.gz' % version
1092 urls = (_CHROMEOS_MIRROR + local_filename,)
1093 hex_sum = 'cb7fd3e58c129494ee86e41baedfec69eb7dafbe'
1094 _build_and_install = ExternalPackage._build_and_install_from_package
1095 _build_and_install_current_dir = (
1096 ExternalPackage._build_and_install_current_dir_noegg)
1097
1098
beepsd9153b52013-01-23 20:52:46 -08001099class _ExternalGitRepo(ExternalPackage):
1100 """
1101 Parent class for any package which needs to pull a git repo.
1102
1103 This class inherits from ExternalPackage only so we can sync git
1104 repos through the build_externals script. We do not reuse any of
1105 ExternalPackage's other methods. Any package that needs a git repo
1106 should subclass this and override build_and_install or fetch as
1107 they see appropriate.
1108 """
1109
Michael Janssena7427612014-11-14 15:44:39 -08001110 os_requirements = {('/usr/bin/git') : 'git-core'}
beepsd9153b52013-01-23 20:52:46 -08001111
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001112 # All the chromiumos projects used on the lab servers should have a 'prod'
1113 # branch used to track the software version deployed in prod.
1114 PROD_BRANCH = 'prod'
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001115 MASTER_BRANCH = 'master'
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001116
beepsd9153b52013-01-23 20:52:46 -08001117 def is_needed(self, unused_install_dir):
1118 """Tell build_externals that we need to fetch."""
1119 # TODO(beeps): check if we're already upto date.
1120 return True
1121
1122
1123 def build_and_install(self, unused_install_dir):
1124 """
1125 Fall through method to install a package.
1126
1127 Overwritten in base classes to pull a git repo.
1128 """
1129 raise NotImplementedError
1130
1131
1132 def fetch(self, unused_dest_dir):
1133 """Fallthrough method to fetch a package."""
1134 return True
1135
1136
1137class HdctoolsRepo(_ExternalGitRepo):
1138 """Clones or updates the hdctools repo."""
1139
Alex Miller0e1217c2013-02-22 10:11:08 -08001140 module_name = 'servo'
beepsd9153b52013-01-23 20:52:46 -08001141 temp_hdctools_dir = tempfile.mktemp(suffix='hdctools')
Alex Miller9fbe67f2013-09-06 15:04:48 -07001142 _GIT_URL = ('https://chromium.googlesource.com/'
beepsd9153b52013-01-23 20:52:46 -08001143 'chromiumos/third_party/hdctools')
1144
1145 def fetch(self, unused_dest_dir):
1146 """
1147 Fetch repo to a temporary location.
1148
1149 We use an intermediate temp directory to stage our
1150 installation because we only care about the servo package.
1151 If we can't get at the top commit hash after fetching
1152 something is wrong. This can happen when we've cloned/pulled
1153 an empty repo. Not something we expect to do.
1154
1155 @parma unused_dest_dir: passed in because we inherit from
1156 ExternalPackage.
1157
1158 @return: True if repo sync was successful.
1159 """
1160 git_repo = revision_control.GitRepo(
1161 self.temp_hdctools_dir,
1162 self._GIT_URL,
1163 None,
beepsaae3f1c2013-03-19 15:49:14 -07001164 abs_work_tree=self.temp_hdctools_dir)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001165 git_repo.reinit_repo_at(self.PROD_BRANCH)
beepsd9153b52013-01-23 20:52:46 -08001166
1167 if git_repo.get_latest_commit_hash():
1168 return True
1169 return False
1170
1171
1172 def build_and_install(self, install_dir):
1173 """Reach into the hdctools repo and rsync only the servo directory."""
1174
1175 servo_dir = os.path.join(self.temp_hdctools_dir, 'servo')
1176 if not os.path.exists(servo_dir):
1177 return False
1178
1179 rv = self._rsync(servo_dir, os.path.join(install_dir, 'servo'))
1180 shutil.rmtree(self.temp_hdctools_dir)
1181 return rv
1182
1183
1184class ChromiteRepo(_ExternalGitRepo):
1185 """Clones or updates the chromite repo."""
1186
Alex Miller9fbe67f2013-09-06 15:04:48 -07001187 _GIT_URL = ('https://chromium.googlesource.com/chromiumos/chromite')
beepsd9153b52013-01-23 20:52:46 -08001188
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001189 def build_and_install(self, install_dir, master_branch=False):
beepsd9153b52013-01-23 20:52:46 -08001190 """
1191 Clone if the repo isn't initialized, pull clean bits if it is.
1192
1193 Unlike it's hdctools counterpart the chromite repo clones master
1194 directly into site-packages. It doesn't use an intermediate temp
Chris Sosa1bf41c42013-02-27 11:34:23 -08001195 directory because it doesn't need installation.
beepsd9153b52013-01-23 20:52:46 -08001196
1197 @param install_dir: destination directory for chromite installation.
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001198 @param master_branch: if True, install master branch. Otherwise,
1199 install prod branch.
beepsd9153b52013-01-23 20:52:46 -08001200 """
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001201 init_branch = (self.MASTER_BRANCH if master_branch
1202 else self.PROD_BRANCH)
beepsd9153b52013-01-23 20:52:46 -08001203 local_chromite_dir = os.path.join(install_dir, 'chromite')
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001204 git_repo = revision_control.GitRepo(
1205 local_chromite_dir,
1206 self._GIT_URL,
1207 abs_work_tree=local_chromite_dir)
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001208 git_repo.reinit_repo_at(init_branch)
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001209
Chris Sosa1bf41c42013-02-27 11:34:23 -08001210
1211 if git_repo.get_latest_commit_hash():
1212 return True
1213 return False
1214
1215
Scott James Remnantbb1a9672014-03-03 14:03:09 -08001216class BtsocketRepo(_ExternalGitRepo):
1217 """Clones or updates the btsocket repo."""
1218
1219 _GIT_URL = ('https://chromium.googlesource.com/'
1220 'chromiumos/platform/btsocket')
1221
1222 def fetch(self, unused_dest_dir):
1223 """
1224 Fetch repo to a temporary location.
1225
1226 We use an intermediate temp directory because we have to build an
1227 egg for installation. If we can't get at the top commit hash after
1228 fetching something is wrong. This can happen when we've cloned/pulled
1229 an empty repo. Not something we expect to do.
1230
1231 @parma unused_dest_dir: passed in because we inherit from
1232 ExternalPackage.
1233
1234 @return: True if repo sync was successful.
1235 """
1236 self.temp_btsocket_dir = autotemp.tempdir(unique_id='btsocket')
1237 try:
1238 git_repo = revision_control.GitRepo(
1239 self.temp_btsocket_dir.name,
1240 self._GIT_URL,
1241 None,
1242 abs_work_tree=self.temp_btsocket_dir.name)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001243 git_repo.reinit_repo_at(self.PROD_BRANCH)
Scott James Remnantbb1a9672014-03-03 14:03:09 -08001244
1245 if git_repo.get_latest_commit_hash():
1246 return True
1247 except:
1248 self.temp_btsocket_dir.clean()
1249 raise
1250
1251 self.temp_btsocket_dir.clean()
1252 return False
1253
1254
1255 def build_and_install(self, install_dir):
1256 """
1257 Install the btsocket module using setup.py
1258
1259 @param install_dir: Target installation directory.
1260
1261 @return: A boolean indicating success of failure.
1262 """
1263 work_dir = os.getcwd()
1264 try:
1265 os.chdir(self.temp_btsocket_dir.name)
1266 rv = self._build_and_install_current_dir_setup_py(install_dir)
1267 finally:
1268 os.chdir(work_dir)
1269 self.temp_btsocket_dir.clean()
J. Richard Barnette428b3442014-07-22 11:38:55 -07001270 return rv
Ningning Xiac73bb7d2018-04-26 18:10:30 -07001271
1272
1273class SkylabInventoryRepo(_ExternalGitRepo):
1274 """Clones or updates the skylab_inventory repo."""
1275
1276 _GIT_URL = ('https://chromium.googlesource.com/chromiumos/infra/'
1277 'skylab_inventory')
1278
1279 # TODO(nxia): create a prod branch for skylab_inventory.
1280 def build_and_install(self, install_dir):
1281 """
1282 @param install_dir: destination directory for skylab_inventory
1283 installation.
1284 """
1285 local_skylab_dir = os.path.join(install_dir, 'infra_skylab_inventory')
1286 git_repo = revision_control.GitRepo(
1287 local_skylab_dir,
1288 self._GIT_URL,
1289 abs_work_tree=local_skylab_dir)
1290 git_repo.reinit_repo_at(self.MASTER_BRANCH)
1291
1292 # The top-level __init__.py for skylab is at venv/skylab_inventory.
Ningning Xiac6367562018-04-30 13:31:34 -07001293 source = os.path.join(local_skylab_dir, 'venv', 'skylab_inventory')
1294 link_name = os.path.join(install_dir, 'skylab_inventory')
1295
1296 if (os.path.exists(link_name) and
1297 os.path.realpath(link_name) != os.path.realpath(source)):
1298 os.remove(link_name)
1299
1300 if not os.path.exists(link_name):
1301 os.symlink(source, link_name)
Ningning Xiac73bb7d2018-04-26 18:10:30 -07001302
1303 if git_repo.get_latest_commit_hash():
1304 return True
1305 return False