blob: c4eca4e607c8cbcccb4ae21a5562515068552a3b [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
Allen Lif9c3e472018-10-05 16:12:00 -0700167 # Check if we're getting a module installed somewhere else,
168 # e.g. on the system.
169 if self.module_name not in self.SYSTEM_MODULES:
170 if (hasattr(module, '__file__')
171 and not module.__file__.startswith(install_dir)):
172 path = module.__file__
173 elif (hasattr(module, '__path__')
Allen Li510d5492018-10-08 17:32:59 -0700174 and module.__path__
175 and not module.__path__[0].startswith(install_dir)):
Allen Lif9c3e472018-10-05 16:12:00 -0700176 path = module.__path__[0]
177 else:
178 logging.warning('module %s has no __file__ or __path__',
179 self.module_name)
180 return True
181 logging.info(
182 'Found %s installed in %s, installing our version in %s',
183 self.module_name, path, install_dir)
Dan Shi7b6297b2015-06-23 13:54:09 -0700184 return True
jamesrenff6e5aa2010-02-12 00:46:40 +0000185 self.installed_version = self._get_installed_version_from_module(module)
xixuanebe46312016-09-07 14:45:49 -0700186 if not self.installed_version:
187 return True
188
jamesrenff6e5aa2010-02-12 00:46:40 +0000189 logging.info('imported %s version %s.', self.module_name,
190 self.installed_version)
Dale Curtis74a314b2011-06-23 14:55:46 -0700191 if hasattr(self, 'minimum_version'):
xixuanebe46312016-09-07 14:45:49 -0700192 return LooseVersion(self.minimum_version) > LooseVersion(
193 self.installed_version)
Dale Curtis74a314b2011-06-23 14:55:46 -0700194 else:
xixuanebe46312016-09-07 14:45:49 -0700195 return LooseVersion(self.version) > LooseVersion(
196 self.installed_version)
jamesrenff6e5aa2010-02-12 00:46:40 +0000197
198
199 def _get_installed_version_from_module(self, module):
200 """Ask our module its version string and return it or '' if unknown."""
201 try:
202 return module.__version__
203 except AttributeError:
204 logging.error('could not get version from %s', module)
205 return ''
206
207
208 def _build_and_install(self, install_dir):
209 """Subclasses MUST provide their own implementation."""
210 raise NotImplementedError
211
212
213 def _build_and_install_current_dir(self, install_dir):
214 """
215 Subclasses that use _build_and_install_from_package() MUST provide
216 their own implementation of this method.
217 """
218 raise NotImplementedError
219
220
221 def build_and_install(self, install_dir):
222 """
223 Builds and installs the package. It must have been fetched already.
224
225 @param install_dir - The package installation directory. If it does
226 not exist it will be created.
227 """
228 if not self.verified_package:
229 raise Error('Must call fetch() first. - %s' % self.name)
230 self._check_os_requirements()
231 return self._build_and_install(install_dir)
232
233
234 def _check_os_requirements(self):
235 if not self.os_requirements:
236 return
237 failed = False
Michael Janssena7427612014-11-14 15:44:39 -0800238 for file_names, package_name in self.os_requirements.iteritems():
239 if not any(os.path.exists(file_name) for file_name in file_names):
jamesrenff6e5aa2010-02-12 00:46:40 +0000240 failed = True
Michael Janssena7427612014-11-14 15:44:39 -0800241 logging.error('Can\'t find %s, %s probably needs it.',
J. Richard Barnette6f7606b2015-06-11 17:26:23 -0700242 ' or '.join(file_names), self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000243 logging.error('Perhaps you need to install something similar '
244 'to the %s package for OS first.', package_name)
245 if failed:
246 raise Error('Missing OS requirements for %s. (see above)' %
247 self.name)
248
249
250 def _build_and_install_current_dir_setup_py(self, install_dir):
251 """For use as a _build_and_install_current_dir implementation."""
252 egg_path = self._build_egg_using_setup_py(setup_py='setup.py')
253 if not egg_path:
254 return False
255 return self._install_from_egg(install_dir, egg_path)
256
257
258 def _build_and_install_current_dir_setupegg_py(self, install_dir):
259 """For use as a _build_and_install_current_dir implementation."""
260 egg_path = self._build_egg_using_setup_py(setup_py='setupegg.py')
261 if not egg_path:
262 return False
263 return self._install_from_egg(install_dir, egg_path)
264
265
266 def _build_and_install_current_dir_noegg(self, install_dir):
267 if not self._build_using_setup_py():
268 return False
269 return self._install_using_setup_py_and_rsync(install_dir)
270
271
xixuand63f4502016-09-12 17:36:55 -0700272 def _get_extension(self, package):
273 """Get extension of package."""
274 valid_package_extensions = ['.tar.gz', '.tar.bz2', '.zip']
275 extension = None
276
277 for ext in valid_package_extensions:
278 if package.endswith(ext):
279 extension = ext
280 break
281
282 if not extension:
283 raise Error('Unexpected package file extension on %s' % package)
284
285 return extension
286
287
jamesrenff6e5aa2010-02-12 00:46:40 +0000288 def _build_and_install_from_package(self, install_dir):
289 """
290 This method may be used as a _build_and_install() implementation
291 for subclasses if they implement _build_and_install_current_dir().
292
293 Extracts the .tar.gz file, chdirs into the extracted directory
294 (which is assumed to match the tar filename) and calls
295 _build_and_isntall_current_dir from there.
296
297 Afterwards the build (regardless of failure) extracted .tar.gz
298 directory is cleaned up.
299
300 @returns True on success, False otherwise.
301
302 @raises OSError If the expected extraction directory does not exist.
303 """
304 self._extract_compressed_package()
xixuand63f4502016-09-12 17:36:55 -0700305 extension = self._get_extension(self.verified_package)
jamesrenff6e5aa2010-02-12 00:46:40 +0000306 os.chdir(os.path.dirname(self.verified_package))
xixuand63f4502016-09-12 17:36:55 -0700307 os.chdir(self.extracted_package_path)
jamesrenff6e5aa2010-02-12 00:46:40 +0000308 extracted_dir = os.getcwd()
309 try:
310 return self._build_and_install_current_dir(install_dir)
311 finally:
312 os.chdir(os.path.join(extracted_dir, '..'))
313 shutil.rmtree(extracted_dir)
314
315
316 def _extract_compressed_package(self):
317 """Extract the fetched compressed .tar or .zip within its directory."""
318 if not self.verified_package:
319 raise Error('Package must have been fetched first.')
320 os.chdir(os.path.dirname(self.verified_package))
321 if self.verified_package.endswith('gz'):
322 status = system("tar -xzf '%s'" % self.verified_package)
323 elif self.verified_package.endswith('bz2'):
324 status = system("tar -xjf '%s'" % self.verified_package)
325 elif self.verified_package.endswith('zip'):
326 status = system("unzip '%s'" % self.verified_package)
327 else:
328 raise Error('Unknown compression suffix on %s.' %
329 self.verified_package)
330 if status:
331 raise Error('tar failed with %s' % (status,))
332
333
334 def _build_using_setup_py(self, setup_py='setup.py'):
335 """
336 Assuming the cwd is the extracted python package, execute a simple
337 python setup.py build.
338
339 @param setup_py - The name of the setup.py file to execute.
340
341 @returns True on success, False otherwise.
342 """
343 if not os.path.exists(setup_py):
344 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
345 status = system("'%s' %s build" % (sys.executable, setup_py))
346 if status:
Dan Shi57631dc2013-02-22 09:54:14 -0800347 logging.error('%s build failed.', self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000348 return False
349 return True
350
351
352 def _build_egg_using_setup_py(self, setup_py='setup.py'):
353 """
354 Assuming the cwd is the extracted python package, execute a simple
355 python setup.py bdist_egg.
356
357 @param setup_py - The name of the setup.py file to execute.
358
359 @returns The relative path to the resulting egg file or '' on failure.
360 """
361 if not os.path.exists(setup_py):
362 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
363 egg_subdir = 'dist'
364 if os.path.isdir(egg_subdir):
365 shutil.rmtree(egg_subdir)
366 status = system("'%s' %s bdist_egg" % (sys.executable, setup_py))
367 if status:
368 logging.error('bdist_egg of setuptools failed.')
369 return ''
370 # I've never seen a bdist_egg lay multiple .egg files.
371 for filename in os.listdir(egg_subdir):
372 if filename.endswith('.egg'):
373 return os.path.join(egg_subdir, filename)
374
375
376 def _install_from_egg(self, install_dir, egg_path):
377 """
378 Install a module from an egg file by unzipping the necessary parts
379 into install_dir.
380
381 @param install_dir - The installation directory.
382 @param egg_path - The pathname of the egg file.
383 """
384 status = system("unzip -q -o -d '%s' '%s'" % (install_dir, egg_path))
385 if status:
386 logging.error('unzip of %s failed', egg_path)
387 return False
Allen Lia9f2a2b2016-08-03 13:44:50 -0700388 egg_info_dir = os.path.join(install_dir, 'EGG-INFO')
389 if os.path.isdir(egg_info_dir):
390 egg_info_new_path = self._get_egg_info_path(install_dir)
391 if egg_info_new_path:
392 if os.path.exists(egg_info_new_path):
393 shutil.rmtree(egg_info_new_path)
394 os.rename(egg_info_dir, egg_info_new_path)
395 else:
396 shutil.rmtree(egg_info_dir)
jamesrenff6e5aa2010-02-12 00:46:40 +0000397 return True
398
399
Allen Lia9f2a2b2016-08-03 13:44:50 -0700400 def _get_egg_info_path(self, install_dir):
401 """Get egg-info path for this package.
402
403 Example path: install_dir/MySQL_python-1.2.3.egg-info
404
405 """
406 if self.dist_name:
407 egg_info_name_part = self.dist_name.replace('-', '_')
408 if self.version:
409 egg_info_filename = '%s-%s.egg-info' % (egg_info_name_part,
410 self.version)
411 else:
412 egg_info_filename = '%s.egg-info' % (egg_info_name_part,)
413 return os.path.join(install_dir, egg_info_filename)
414 else:
415 return None
416
417
jamesrenff6e5aa2010-02-12 00:46:40 +0000418 def _get_temp_dir(self):
419 return tempfile.mkdtemp(dir='/var/tmp')
420
421
422 def _site_packages_path(self, temp_dir):
423 # This makes assumptions about what python setup.py install
424 # does when given a prefix. Is this always correct?
425 python_xy = 'python%s' % sys.version[:3]
426 return os.path.join(temp_dir, 'lib', python_xy, 'site-packages')
427
428
beepsd9153b52013-01-23 20:52:46 -0800429 def _rsync (self, temp_site_dir, install_dir):
430 """Rsync contents. """
431 status = system("rsync -r '%s/' '%s/'" %
432 (os.path.normpath(temp_site_dir),
433 os.path.normpath(install_dir)))
434 if status:
435 logging.error('%s rsync to install_dir failed.', self.name)
436 return False
437 return True
438
439
jamesrenff6e5aa2010-02-12 00:46:40 +0000440 def _install_using_setup_py_and_rsync(self, install_dir,
441 setup_py='setup.py',
442 temp_dir=None):
443 """
444 Assuming the cwd is the extracted python package, execute a simple:
445
446 python setup.py install --prefix=BLA
447
448 BLA will be a temporary directory that everything installed will
449 be picked out of and rsynced to the appropriate place under
450 install_dir afterwards.
451
452 Afterwards, it deconstructs the extra lib/pythonX.Y/site-packages/
453 directory tree that setuptools created and moves all installed
454 site-packages directly up into install_dir itself.
455
456 @param install_dir the directory for the install to happen under.
457 @param setup_py - The name of the setup.py file to execute.
458
459 @returns True on success, False otherwise.
460 """
461 if not os.path.exists(setup_py):
462 raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
463
464 if temp_dir is None:
465 temp_dir = self._get_temp_dir()
466
467 try:
468 status = system("'%s' %s install --no-compile --prefix='%s'"
469 % (sys.executable, setup_py, temp_dir))
470 if status:
Dan Shi57631dc2013-02-22 09:54:14 -0800471 logging.error('%s install failed.', self.name)
jamesrenff6e5aa2010-02-12 00:46:40 +0000472 return False
473
474 if os.path.isdir(os.path.join(temp_dir, 'lib')):
475 # NOTE: This ignores anything outside of the lib/ dir that
476 # was installed.
477 temp_site_dir = self._site_packages_path(temp_dir)
478 else:
479 temp_site_dir = temp_dir
480
beepsd9153b52013-01-23 20:52:46 -0800481 return self._rsync(temp_site_dir, install_dir)
jamesrenff6e5aa2010-02-12 00:46:40 +0000482 finally:
483 shutil.rmtree(temp_dir)
484
485
486
487 def _build_using_make(self, install_dir):
488 """Build the current package using configure/make.
489
490 @returns True on success, False otherwise.
491 """
492 install_prefix = os.path.join(install_dir, 'usr', 'local')
493 status = system('./configure --prefix=%s' % install_prefix)
494 if status:
495 logging.error('./configure failed for %s', self.name)
496 return False
497 status = system('make')
498 if status:
499 logging.error('make failed for %s', self.name)
500 return False
501 status = system('make check')
502 if status:
503 logging.error('make check failed for %s', self.name)
504 return False
505 return True
506
507
508 def _install_using_make(self):
509 """Install the current package using make install.
510
511 Assumes the install path was set up while running ./configure (in
512 _build_using_make()).
513
514 @returns True on success, False otherwise.
515 """
516 status = system('make install')
517 return status == 0
518
519
520 def fetch(self, dest_dir):
521 """
522 Fetch the package from one its URLs and save it in dest_dir.
523
524 If the the package already exists in dest_dir and the checksum
525 matches this code will not fetch it again.
526
527 Sets the 'verified_package' attribute with the destination pathname.
528
529 @param dest_dir - The destination directory to save the local file.
530 If it does not exist it will be created.
531
532 @returns A boolean indicating if we the package is now in dest_dir.
533 @raises FetchError - When something unexpected happens.
534 """
535 if not os.path.exists(dest_dir):
536 os.makedirs(dest_dir)
537 local_path = os.path.join(dest_dir, self.local_filename)
538
539 # If the package exists, verify its checksum and be happy if it is good.
540 if os.path.exists(local_path):
541 actual_hex_sum = _checksum_file(local_path)
542 if self.hex_sum == actual_hex_sum:
543 logging.info('Good checksum for existing %s package.',
544 self.name)
545 self.verified_package = local_path
546 return True
547 logging.warning('Bad checksum for existing %s package. '
548 'Re-downloading', self.name)
549 os.rename(local_path, local_path + '.wrong-checksum')
550
551 # Download the package from one of its urls, rejecting any if the
552 # checksum does not match.
553 for url in self.urls:
554 logging.info('Fetching %s', url)
555 try:
556 url_file = urllib2.urlopen(url)
557 except (urllib2.URLError, EnvironmentError):
558 logging.warning('Could not fetch %s package from %s.',
559 self.name, url)
560 continue
Dan Shi57631dc2013-02-22 09:54:14 -0800561
jamesrenff6e5aa2010-02-12 00:46:40 +0000562 data_length = int(url_file.info().get('Content-Length',
563 _MAX_PACKAGE_SIZE))
564 if data_length <= 0 or data_length > _MAX_PACKAGE_SIZE:
565 raise FetchError('%s from %s fails Content-Length %d '
566 'sanity check.' % (self.name, url,
567 data_length))
568 checksum = utils.hash('sha1')
569 total_read = 0
570 output = open(local_path, 'wb')
571 try:
572 while total_read < data_length:
573 data = url_file.read(_READ_SIZE)
574 if not data:
575 break
576 output.write(data)
577 checksum.update(data)
578 total_read += len(data)
579 finally:
580 output.close()
581 if self.hex_sum != checksum.hexdigest():
582 logging.warning('Bad checksum for %s fetched from %s.',
583 self.name, url)
584 logging.warning('Got %s', checksum.hexdigest())
585 logging.warning('Expected %s', self.hex_sum)
586 os.unlink(local_path)
587 continue
588 logging.info('Good checksum.')
589 self.verified_package = local_path
590 return True
591 else:
592 return False
593
594
595# NOTE: This class definition must come -before- all other ExternalPackage
596# classes that need to use this version of setuptools so that is is inserted
597# into the ExternalPackage.subclasses list before them.
598class SetuptoolsPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800599 """setuptools package"""
jamesrenff6e5aa2010-02-12 00:46:40 +0000600 # For all known setuptools releases a string compare works for the
601 # version string. Hopefully they never release a 0.10. (Their own
602 # version comparison code would break if they did.)
Dan Shi16c0a502015-07-14 17:29:48 -0700603 # Any system with setuptools > 18.0.1 is fine. If none installed, then
Dale Curtis74a314b2011-06-23 14:55:46 -0700604 # try to install the latest found on the upstream.
Dan Shi16c0a502015-07-14 17:29:48 -0700605 minimum_version = '18.0.1'
606 version = '18.0.1'
Aviv Keshetaf98d472017-10-03 17:17:56 -0700607 urls = (_CHROMEOS_MIRROR + 'setuptools-%s.tar.gz' % (version,),)
jamesrenff6e5aa2010-02-12 00:46:40 +0000608 local_filename = 'setuptools-%s.tar.gz' % version
Dan Shi16c0a502015-07-14 17:29:48 -0700609 hex_sum = 'ebc4fe81b7f6d61d923d9519f589903824044f52'
jamesrenff6e5aa2010-02-12 00:46:40 +0000610
611 SUDO_SLEEP_DELAY = 15
612
613
614 def _build_and_install(self, install_dir):
615 """Install setuptools on the system."""
616 logging.info('NOTE: setuptools install does not use install_dir.')
617 return self._build_and_install_from_package(install_dir)
618
619
620 def _build_and_install_current_dir(self, install_dir):
621 egg_path = self._build_egg_using_setup_py()
622 if not egg_path:
623 return False
624
625 print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n'
626 print 'About to run sudo to install setuptools', self.version
627 print 'on your system for use by', sys.executable, '\n'
628 print '!! ^C within', self.SUDO_SLEEP_DELAY, 'seconds to abort.\n'
629 time.sleep(self.SUDO_SLEEP_DELAY)
630
631 # Copy the egg to the local filesystem /var/tmp so that root can
632 # access it properly (avoid NFS squashroot issues).
633 temp_dir = self._get_temp_dir()
634 try:
635 shutil.copy(egg_path, temp_dir)
636 egg_name = os.path.split(egg_path)[1]
637 temp_egg = os.path.join(temp_dir, egg_name)
638 p = subprocess.Popen(['sudo', '/bin/sh', temp_egg],
639 stdout=subprocess.PIPE)
640 regex = re.compile('Copying (.*?) to (.*?)\n')
641 match = regex.search(p.communicate()[0])
642 status = p.wait()
643
644 if match:
645 compiled = os.path.join(match.group(2), match.group(1))
646 os.system("sudo chmod a+r '%s'" % compiled)
647 finally:
648 shutil.rmtree(temp_dir)
649
650 if status:
651 logging.error('install of setuptools from egg failed.')
652 return False
653 return True
654
655
656class MySQLdbPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800657 """mysql package, used in scheduler."""
jamesrenff6e5aa2010-02-12 00:46:40 +0000658 module_name = 'MySQLdb'
Alex Miller47d61282013-04-17 13:53:58 -0700659 version = '1.2.3'
jamesrenff6e5aa2010-02-12 00:46:40 +0000660 local_filename = 'MySQL-python-%s.tar.gz' % version
Aviv Keshet34fad2f2017-09-29 14:06:03 -0700661 urls = ('http://commondatastorage.googleapis.com/chromeos-mirror/gentoo/'
662 'distfiles/%s' % local_filename,)
Alex Miller47d61282013-04-17 13:53:58 -0700663 hex_sum = '3511bb8c57c6016eeafa531d5c3ea4b548915e3c'
jamesrenff6e5aa2010-02-12 00:46:40 +0000664
665 _build_and_install_current_dir = (
666 ExternalPackage._build_and_install_current_dir_setup_py)
667
668
669 def _build_and_install(self, install_dir):
670 if not os.path.exists('/usr/bin/mysql_config'):
Allen Li74e4b282018-01-19 14:03:45 -0800671 error_msg = '''\
672You need to install /usr/bin/mysql_config.
673On recent Debian based distros, run: \
674sudo apt-get install libmariadbclient-dev-compat
675On older Debian based distros, run: sudo apt-get install libmysqlclient15-dev
676'''
Dan Shi16c0a502015-07-14 17:29:48 -0700677 logging.error(error_msg)
678 return False, error_msg
jamesrenff6e5aa2010-02-12 00:46:40 +0000679 return self._build_and_install_from_package(install_dir)
680
681
682class DjangoPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800683 """django package."""
Alex Miller47d61282013-04-17 13:53:58 -0700684 version = '1.5.1'
jamesrenff6e5aa2010-02-12 00:46:40 +0000685 local_filename = 'Django-%s.tar.gz' % version
Aviv Keshetaf98d472017-10-03 17:17:56 -0700686 urls = (_CHROMEOS_MIRROR + local_filename,)
Alex Miller47d61282013-04-17 13:53:58 -0700687 hex_sum = '0ab97b90c4c79636e56337f426f1e875faccbba1'
jamesrenff6e5aa2010-02-12 00:46:40 +0000688
689 _build_and_install = ExternalPackage._build_and_install_from_package
690 _build_and_install_current_dir = (
691 ExternalPackage._build_and_install_current_dir_noegg)
692
693
694 def _get_installed_version_from_module(self, module):
695 try:
696 return module.get_version().split()[0]
697 except AttributeError:
698 return '0.9.6'
699
700
701
702class NumpyPackage(ExternalPackage):
Dan Shi57631dc2013-02-22 09:54:14 -0800703 """numpy package, required by matploglib."""
Scott Zawalskic2c9a012013-03-06 09:13:26 -0500704 version = '1.7.0'
jamesrenff6e5aa2010-02-12 00:46:40 +0000705 local_filename = 'numpy-%s.tar.gz' % version
Aviv Keshetaf98d472017-10-03 17:17:56 -0700706 urls = (_CHROMEOS_MIRROR + local_filename,)
Scott Zawalskic2c9a012013-03-06 09:13:26 -0500707 hex_sum = 'ba328985f20390b0f969a5be2a6e1141d5752cf9'
jamesrenff6e5aa2010-02-12 00:46:40 +0000708
709 _build_and_install = ExternalPackage._build_and_install_from_package
710 _build_and_install_current_dir = (
711 ExternalPackage._build_and_install_current_dir_setupegg_py)
712
713
jamesrenff6e5aa2010-02-12 00:46:40 +0000714
beeps32a63082013-08-22 14:02:29 -0700715class JsonRPCLib(ExternalPackage):
716 """jsonrpclib package"""
717 version = '0.1.3'
718 module_name = 'jsonrpclib'
719 local_filename = '%s-%s.tar.gz' % (module_name, version)
Aviv Keshetaf98d472017-10-03 17:17:56 -0700720 urls = (_CHROMEOS_MIRROR + local_filename,)
beeps32a63082013-08-22 14:02:29 -0700721 hex_sum = '431714ed19ab677f641ce5d678a6a95016f5c452'
722
723 def _get_installed_version_from_module(self, module):
724 # jsonrpclib doesn't contain a proper version
725 return self.version
726
727 _build_and_install = ExternalPackage._build_and_install_from_package
728 _build_and_install_current_dir = (
729 ExternalPackage._build_and_install_current_dir_noegg)
730
731
jamesrenff6e5aa2010-02-12 00:46:40 +0000732class GwtPackage(ExternalPackage):
733 """Fetch and extract a local copy of GWT used to build the frontend."""
734
Dale Curtis74a314b2011-06-23 14:55:46 -0700735 version = '2.3.0'
jamesren012d0322010-04-30 20:21:29 +0000736 local_filename = 'gwt-%s.zip' % version
Aviv Keshetdeb7a942017-11-05 17:58:45 -0800737 urls = (_CHROMEOS_MIRROR + local_filename,)
Dale Curtis74a314b2011-06-23 14:55:46 -0700738 hex_sum = 'd51fce9166e6b31349659ffca89baf93e39bc84b'
jamesrenff6e5aa2010-02-12 00:46:40 +0000739 name = 'gwt'
740 about_filename = 'about.txt'
741 module_name = None # Not a Python module.
742
743
744 def is_needed(self, install_dir):
745 gwt_dir = os.path.join(install_dir, self.name)
746 about_file = os.path.join(install_dir, self.name, self.about_filename)
747
748 if not os.path.exists(gwt_dir) or not os.path.exists(about_file):
749 logging.info('gwt not installed for autotest')
750 return True
751
752 f = open(about_file, 'r')
753 version_line = f.readline()
754 f.close()
755
756 match = re.match(r'Google Web Toolkit (.*)', version_line)
757 if not match:
758 logging.info('did not find gwt version')
759 return True
760
761 logging.info('found gwt version %s', match.group(1))
762 return match.group(1) != self.version
763
764
jamesrenca2a9002010-04-20 22:33:22 +0000765 def _build_and_install(self, install_dir):
jamesrenff6e5aa2010-02-12 00:46:40 +0000766 os.chdir(install_dir)
767 self._extract_compressed_package()
jamesren012d0322010-04-30 20:21:29 +0000768 extracted_dir = self.local_filename[:-len('.zip')]
jamesrenff6e5aa2010-02-12 00:46:40 +0000769 target_dir = os.path.join(install_dir, self.name)
770 if os.path.exists(target_dir):
771 shutil.rmtree(target_dir)
772 os.rename(extracted_dir, target_dir)
773 return True
774
775
Alex Millere7b6e8b2013-02-13 17:34:04 -0800776class PyudevPackage(ExternalPackage):
777 """
778 pyudev module
779
780 Used in unittests.
781 """
782 version = '0.16.1'
783 url_filename = 'pyudev-%s.tar.gz' % version
784 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700785 urls = (_CHROMEOS_MIRROR + local_filename,)
Alex Millere7b6e8b2013-02-13 17:34:04 -0800786 hex_sum = 'b36bc5c553ce9b56d32a5e45063a2c88156771c0'
787
788 _build_and_install = ExternalPackage._build_and_install_from_package
789 _build_and_install_current_dir = (
790 ExternalPackage._build_and_install_current_dir_setup_py)
791
792
793class PyMoxPackage(ExternalPackage):
794 """
795 mox module
796
797 Used in unittests.
798 """
799 module_name = 'mox'
800 version = '0.5.3'
Aviv Keshet96ccfda2017-11-03 12:47:25 -0700801 # Note: url_filename does not match local_filename, because of
802 # an uncontrolled fork at some point in time of mox versions.
803 url_filename = 'mox-%s-autotest.tar.gz' % version
804 local_filename = 'mox-%s.tar.gz' % version
805 urls = (_CHROMEOS_MIRROR + url_filename,)
Alex Millere7b6e8b2013-02-13 17:34:04 -0800806 hex_sum = '1c502d2c0a8aefbba2c7f385a83d33e7d822452a'
807
808 _build_and_install = ExternalPackage._build_and_install_from_package
809 _build_and_install_current_dir = (
810 ExternalPackage._build_and_install_current_dir_noegg)
811
812 def _get_installed_version_from_module(self, module):
813 # mox doesn't contain a proper version
814 return self.version
815
816
Jason Abele2371d1a2013-11-22 12:18:18 -0800817class PySeleniumPackage(ExternalPackage):
818 """
819 selenium module
820
821 Used in wifi_interop suite.
822 """
823 module_name = 'selenium'
824 version = '2.37.2'
825 url_filename = 'selenium-%s.tar.gz' % version
826 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700827 urls = (_CHROMEOS_MIRROR + local_filename,)
Jason Abele2371d1a2013-11-22 12:18:18 -0800828 hex_sum = '66946d5349e36d946daaad625c83c30c11609e36'
829
830 _build_and_install = ExternalPackage._build_and_install_from_package
831 _build_and_install_current_dir = (
832 ExternalPackage._build_and_install_current_dir_setup_py)
833
834
Simran Basid6b83772014-01-06 16:31:30 -0800835class FaultHandlerPackage(ExternalPackage):
836 """
837 faulthandler module
838 """
839 module_name = 'faulthandler'
840 version = '2.3'
841 url_filename = '%s-%s.tar.gz' % (module_name, version)
842 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700843 urls = (_CHROMEOS_MIRROR + local_filename,)
Simran Basid6b83772014-01-06 16:31:30 -0800844 hex_sum = 'efb30c068414fba9df892e48fcf86170cbf53589'
845
846 _build_and_install = ExternalPackage._build_and_install_from_package
847 _build_and_install_current_dir = (
848 ExternalPackage._build_and_install_current_dir_noegg)
849
850
Alex Millera87e5482014-06-09 16:04:49 -0700851class PsutilPackage(ExternalPackage):
852 """
853 psutil module
854 """
855 module_name = 'psutil'
856 version = '2.1.1'
857 url_filename = '%s-%s.tar.gz' % (module_name, version)
858 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700859 urls = (_CHROMEOS_MIRROR + local_filename,)
Alex Millera87e5482014-06-09 16:04:49 -0700860 hex_sum = '0c20a20ed316e69f2b0881530439213988229916'
861
862 _build_and_install = ExternalPackage._build_and_install_from_package
863 _build_and_install_current_dir = (
864 ExternalPackage._build_and_install_current_dir_setup_py)
865
866
Michael Liangd2d294c2014-06-24 15:24:49 -0700867class ElasticSearchPackage(ExternalPackage):
868 """elasticsearch-py package."""
Dan Shicae83c72015-07-22 17:58:21 -0700869 version = '1.6.0'
Michael Liangd2d294c2014-06-24 15:24:49 -0700870 url_filename = 'elasticsearch-%s.tar.gz' % version
871 local_filename = url_filename
Michael Liang5934b712014-08-12 15:45:14 -0700872 urls = ('https://pypi.python.org/packages/source/e/elasticsearch/%s' %
873 (url_filename),)
Dan Shicae83c72015-07-22 17:58:21 -0700874 hex_sum = '3e676c96f47935b1f52df82df3969564bd356b1c'
Michael Liangd2d294c2014-06-24 15:24:49 -0700875 _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
xixuanebe46312016-09-07 14:45:49 -0700879 def _get_installed_version_from_module(self, module):
880 # Elastic's version format is like tuple (1, 6, 0), which needs to be
881 # transferred to 1.6.0.
882 try:
883 return '.'.join(str(i) for i in module.__version__)
884 except:
885 return self.version
886
Michael Liangd2d294c2014-06-24 15:24:49 -0700887
Michael Liang5934b712014-08-12 15:45:14 -0700888class Urllib3Package(ExternalPackage):
889 """elasticsearch-py package."""
890 version = '1.9'
891 url_filename = 'urllib3-%s.tar.gz' % version
892 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700893 urls = (_CHROMEOS_MIRROR + local_filename,)
Michael Liang5934b712014-08-12 15:45:14 -0700894 hex_sum = '9522197efb2a2b49ce804de3a515f06d97b6602f'
895 _build_and_install = ExternalPackage._build_and_install_from_package
896 _build_and_install_current_dir = (
897 ExternalPackage._build_and_install_current_dir_setup_py)
898
Wai-Hong Tam8ce47ea2017-05-12 11:33:43 -0700899class ImagingLibraryPackage(ExternalPackage):
900 """Python Imaging Library (PIL)."""
901 version = '1.1.7'
902 url_filename = 'Imaging-%s.tar.gz' % version
903 local_filename = url_filename
904 urls = ('http://commondatastorage.googleapis.com/chromeos-mirror/gentoo/'
905 'distfiles/%s' % url_filename,)
906 hex_sum = '76c37504251171fda8da8e63ecb8bc42a69a5c81'
907
908 def _build_and_install(self, install_dir):
909 #The path of zlib library might be different from what PIL setup.py is
910 #expected. Following change does the best attempt to link the library
911 #to a path PIL setup.py will try.
912 libz_possible_path = '/usr/lib/x86_64-linux-gnu/libz.so'
913 libz_expected_path = '/usr/lib/libz.so'
Aviv Keshetb6fbaa82019-04-26 13:25:39 -0700914 # TODO(crbug.com/957186): this sudo command fails if build_externals
915 # is running in non-interactive mode, and requires a workaround when
916 # running within a docker build process. Remove this operation, or
917 # remove this entire package.
Wai-Hong Tam8ce47ea2017-05-12 11:33:43 -0700918 if (os.path.exists(libz_possible_path) and
919 not os.path.exists(libz_expected_path)):
920 utils.run('sudo ln -s %s %s' %
921 (libz_possible_path, libz_expected_path))
922 return self._build_and_install_from_package(install_dir)
923
924 _build_and_install_current_dir = (
925 ExternalPackage._build_and_install_current_dir_noegg)
J. Richard Barnette428b3442014-07-22 11:38:55 -0700926
927
Allen Lia9f2a2b2016-08-03 13:44:50 -0700928class AstroidPackage(ExternalPackage):
929 """astroid package."""
Allen Li4c275d22017-07-19 11:56:24 -0700930 version = '1.5.3'
Allen Lia9f2a2b2016-08-03 13:44:50 -0700931 url_filename = 'astroid-%s.tar.gz' % version
932 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700933 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -0700934 hex_sum = 'e654225ab5bd2788e5e246b156910990bf33cde6'
935 _build_and_install = ExternalPackage._build_and_install_from_package
936 _build_and_install_current_dir = (
937 ExternalPackage._build_and_install_current_dir_setup_py)
938
939
940class LazyObjectProxyPackage(ExternalPackage):
941 """lazy-object-proxy package (dependency for astroid)."""
942 version = '1.3.1'
943 url_filename = 'lazy-object-proxy-%s.tar.gz' % version
944 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700945 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -0700946 hex_sum = '984828d8f672986ca926373986214d7057b772fb'
947 _build_and_install = ExternalPackage._build_and_install_from_package
948 _build_and_install_current_dir = (
949 ExternalPackage._build_and_install_current_dir_setup_py)
950
951
952class SingleDispatchPackage(ExternalPackage):
953 """singledispatch package (dependency for astroid)."""
954 version = '3.4.0.3'
955 url_filename = 'singledispatch-%s.tar.gz' % version
956 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700957 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -0700958 hex_sum = 'f93241b06754a612af8bb7aa208c4d1805637022'
959 _build_and_install = ExternalPackage._build_and_install_from_package
960 _build_and_install_current_dir = (
961 ExternalPackage._build_and_install_current_dir_setup_py)
962
963
964class Enum34Package(ExternalPackage):
965 """enum34 package (dependency for astroid)."""
966 version = '1.1.6'
967 url_filename = 'enum34-%s.tar.gz' % version
968 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700969 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -0700970 hex_sum = '014ef5878333ff91099893d615192c8cd0b1525a'
971 _build_and_install = ExternalPackage._build_and_install_from_package
972 _build_and_install_current_dir = (
973 ExternalPackage._build_and_install_current_dir_setup_py)
974
975
976class WraptPackage(ExternalPackage):
977 """wrapt package (dependency for astroid)."""
978 version = '1.10.10'
979 url_filename = 'wrapt-%s.tar.gz' % version
980 local_filename = url_filename
981 #md5=97365e906afa8b431f266866ec4e2e18
982 urls = ('https://pypi.python.org/packages/a3/bb/'
983 '525e9de0a220060394f4aa34fdf6200853581803d92714ae41fc3556e7d7/%s' %
984 (url_filename),)
985 hex_sum = '6be4f1bb50db879863f4247692360eb830a3eb33'
986 _build_and_install = ExternalPackage._build_and_install_from_package
987 _build_and_install_current_dir = (
988 ExternalPackage._build_and_install_current_dir_noegg)
989
990
991class SixPackage(ExternalPackage):
992 """six package (dependency for astroid)."""
993 version = '1.10.0'
994 url_filename = 'six-%s.tar.gz' % version
995 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -0700996 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -0700997 hex_sum = '30d480d2e352e8e4c2aae042cf1bf33368ff0920'
998 _build_and_install = ExternalPackage._build_and_install_from_package
999 _build_and_install_current_dir = (
1000 ExternalPackage._build_and_install_current_dir_setup_py)
1001
1002
1003class LruCachePackage(ExternalPackage):
1004 """backports.functools_lru_cache package (dependency for astroid)."""
1005 version = '1.4'
1006 url_filename = 'backports.functools_lru_cache-%s.tar.gz' % version
1007 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -07001008 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -07001009 hex_sum = '8a546e7887e961c2873c9b053f4e2cd2a96bd71d'
Allen Lia9f2a2b2016-08-03 13:44:50 -07001010 _build_and_install = ExternalPackage._build_and_install_from_package
1011 _build_and_install_current_dir = (
1012 ExternalPackage._build_and_install_current_dir_setup_py)
1013
1014
1015class LogilabCommonPackage(ExternalPackage):
1016 """logilab-common package."""
1017 version = '1.2.2'
1018 module_name = 'logilab'
1019 url_filename = 'logilab-common-%s.tar.gz' % version
1020 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -07001021 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Lia9f2a2b2016-08-03 13:44:50 -07001022 hex_sum = 'ecad2d10c31dcf183c8bed87b6ec35e7ed397d27'
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
Dan Shiff5b5ed2016-06-10 10:25:24 -07001028class PyLintPackage(ExternalPackage):
1029 """pylint package."""
Allen Li4c275d22017-07-19 11:56:24 -07001030 version = '1.7.2'
Dan Shiff5b5ed2016-06-10 10:25:24 -07001031 url_filename = 'pylint-%s.tar.gz' % version
1032 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -07001033 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -07001034 hex_sum = '42d8b9394e5a485377ae128b01350f25d8b131e0'
1035 _build_and_install = ExternalPackage._build_and_install_from_package
1036 _build_and_install_current_dir = (
1037 ExternalPackage._build_and_install_current_dir_setup_py)
1038
1039
1040class ConfigParserPackage(ExternalPackage):
1041 """configparser package (dependency for pylint)."""
1042 version = '3.5.0'
1043 url_filename = 'configparser-%s.tar.gz' % version
1044 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -07001045 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -07001046 hex_sum = '8ee6b29c6a11977c0e094da1d4f5f71e7e7ac78b'
1047 _build_and_install = ExternalPackage._build_and_install_from_package
1048 _build_and_install_current_dir = (
1049 ExternalPackage._build_and_install_current_dir_setup_py)
1050
1051
1052class IsortPackage(ExternalPackage):
1053 """isort package (dependency for pylint)."""
1054 version = '4.2.15'
1055 url_filename = 'isort-%s.tar.gz' % version
1056 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -07001057 urls = (_CHROMEOS_MIRROR + local_filename,)
Allen Li4c275d22017-07-19 11:56:24 -07001058 hex_sum = 'acacc36e476b70e13e6fda812c193f4c3c187781'
Dan Shiff5b5ed2016-06-10 10:25:24 -07001059 _build_and_install = ExternalPackage._build_and_install_from_package
1060 _build_and_install_current_dir = (
1061 ExternalPackage._build_and_install_current_dir_setup_py)
1062
1063
Daniel Erata749a792018-03-26 12:28:17 -07001064class DateutilPackage(ExternalPackage):
1065 """python-dateutil package."""
1066 version = '2.6.1'
1067 local_filename = 'python-dateutil-%s.tar.gz' % version
1068 urls = (_CHROMEOS_MIRROR + local_filename,)
1069 hex_sum = 'db2ace298dee7e47fd720ed03eb790885347bf4e'
1070
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
Benny Peake4d750422017-03-06 11:53:57 -08001076class Pytz(ExternalPackage):
1077 """Pytz package."""
1078 version = '2016.10'
1079 url_filename = 'pytz-%s.tar.gz' % version
1080 local_filename = url_filename
1081 #md5=cc9f16ba436efabdcef3c4d32ae4919c
1082 urls = ('https://pypi.python.org/packages/42/00/'
1083 '5c89fc6c9b305df84def61863528e899e9dccb196f8438f6cbe960758fc5/%s' %
1084 (url_filename),)
1085 hex_sum = '8d63f1e9b1ee862841b990a7d8ad1d4508d9f0be'
1086 _build_and_install = ExternalPackage._build_and_install_from_package
1087 _build_and_install_current_dir = (
1088 ExternalPackage._build_and_install_current_dir_setup_py)
1089
1090
1091class Tzlocal(ExternalPackage):
1092 """Tzlocal package."""
1093 version = '1.3'
1094 url_filename = 'tzlocal-%s.tar.gz' % version
1095 local_filename = url_filename
Aviv Keshetaf98d472017-10-03 17:17:56 -07001096 urls = (_CHROMEOS_MIRROR + local_filename,)
Benny Peake4d750422017-03-06 11:53:57 -08001097 hex_sum = '730e9d7112335865a1dcfabec69c8c3086be424f'
1098 _build_and_install = ExternalPackage._build_and_install_from_package
1099 _build_and_install_current_dir = (
1100 ExternalPackage._build_and_install_current_dir_setup_py)
1101
1102
Rohit Makasanab822a0c2017-11-29 15:35:17 -08001103class PyYAMLPackage(ExternalPackage):
1104 """pyyaml package."""
1105 version = '3.12'
1106 local_filename = 'PyYAML-%s.tar.gz' % version
1107 urls = (_CHROMEOS_MIRROR + local_filename,)
1108 hex_sum = 'cb7fd3e58c129494ee86e41baedfec69eb7dafbe'
1109 _build_and_install = ExternalPackage._build_and_install_from_package
1110 _build_and_install_current_dir = (
1111 ExternalPackage._build_and_install_current_dir_noegg)
1112
1113
beepsd9153b52013-01-23 20:52:46 -08001114class _ExternalGitRepo(ExternalPackage):
1115 """
1116 Parent class for any package which needs to pull a git repo.
1117
1118 This class inherits from ExternalPackage only so we can sync git
1119 repos through the build_externals script. We do not reuse any of
1120 ExternalPackage's other methods. Any package that needs a git repo
1121 should subclass this and override build_and_install or fetch as
1122 they see appropriate.
1123 """
1124
Michael Janssena7427612014-11-14 15:44:39 -08001125 os_requirements = {('/usr/bin/git') : 'git-core'}
beepsd9153b52013-01-23 20:52:46 -08001126
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001127 # All the chromiumos projects used on the lab servers should have a 'prod'
1128 # branch used to track the software version deployed in prod.
1129 PROD_BRANCH = 'prod'
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001130 MASTER_BRANCH = 'master'
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001131
beepsd9153b52013-01-23 20:52:46 -08001132 def is_needed(self, unused_install_dir):
1133 """Tell build_externals that we need to fetch."""
1134 # TODO(beeps): check if we're already upto date.
1135 return True
1136
1137
1138 def build_and_install(self, unused_install_dir):
1139 """
1140 Fall through method to install a package.
1141
1142 Overwritten in base classes to pull a git repo.
1143 """
1144 raise NotImplementedError
1145
1146
1147 def fetch(self, unused_dest_dir):
1148 """Fallthrough method to fetch a package."""
1149 return True
1150
1151
1152class HdctoolsRepo(_ExternalGitRepo):
1153 """Clones or updates the hdctools repo."""
1154
Alex Miller0e1217c2013-02-22 10:11:08 -08001155 module_name = 'servo'
beepsd9153b52013-01-23 20:52:46 -08001156 temp_hdctools_dir = tempfile.mktemp(suffix='hdctools')
Alex Miller9fbe67f2013-09-06 15:04:48 -07001157 _GIT_URL = ('https://chromium.googlesource.com/'
beepsd9153b52013-01-23 20:52:46 -08001158 'chromiumos/third_party/hdctools')
1159
1160 def fetch(self, unused_dest_dir):
1161 """
1162 Fetch repo to a temporary location.
1163
1164 We use an intermediate temp directory to stage our
1165 installation because we only care about the servo package.
1166 If we can't get at the top commit hash after fetching
1167 something is wrong. This can happen when we've cloned/pulled
1168 an empty repo. Not something we expect to do.
1169
1170 @parma unused_dest_dir: passed in because we inherit from
1171 ExternalPackage.
1172
1173 @return: True if repo sync was successful.
1174 """
1175 git_repo = revision_control.GitRepo(
1176 self.temp_hdctools_dir,
1177 self._GIT_URL,
1178 None,
beepsaae3f1c2013-03-19 15:49:14 -07001179 abs_work_tree=self.temp_hdctools_dir)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001180 git_repo.reinit_repo_at(self.PROD_BRANCH)
beepsd9153b52013-01-23 20:52:46 -08001181
1182 if git_repo.get_latest_commit_hash():
1183 return True
1184 return False
1185
1186
1187 def build_and_install(self, install_dir):
1188 """Reach into the hdctools repo and rsync only the servo directory."""
1189
1190 servo_dir = os.path.join(self.temp_hdctools_dir, 'servo')
1191 if not os.path.exists(servo_dir):
1192 return False
1193
1194 rv = self._rsync(servo_dir, os.path.join(install_dir, 'servo'))
1195 shutil.rmtree(self.temp_hdctools_dir)
1196 return rv
1197
1198
1199class ChromiteRepo(_ExternalGitRepo):
1200 """Clones or updates the chromite repo."""
1201
Alex Miller9fbe67f2013-09-06 15:04:48 -07001202 _GIT_URL = ('https://chromium.googlesource.com/chromiumos/chromite')
beepsd9153b52013-01-23 20:52:46 -08001203
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001204 def build_and_install(self, install_dir, master_branch=False):
beepsd9153b52013-01-23 20:52:46 -08001205 """
1206 Clone if the repo isn't initialized, pull clean bits if it is.
1207
1208 Unlike it's hdctools counterpart the chromite repo clones master
1209 directly into site-packages. It doesn't use an intermediate temp
Chris Sosa1bf41c42013-02-27 11:34:23 -08001210 directory because it doesn't need installation.
beepsd9153b52013-01-23 20:52:46 -08001211
1212 @param install_dir: destination directory for chromite installation.
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001213 @param master_branch: if True, install master branch. Otherwise,
1214 install prod branch.
beepsd9153b52013-01-23 20:52:46 -08001215 """
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001216 init_branch = (self.MASTER_BRANCH if master_branch
1217 else self.PROD_BRANCH)
beepsd9153b52013-01-23 20:52:46 -08001218 local_chromite_dir = os.path.join(install_dir, 'chromite')
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001219 git_repo = revision_control.GitRepo(
1220 local_chromite_dir,
1221 self._GIT_URL,
1222 abs_work_tree=local_chromite_dir)
Shuqian Zhao697f7ed2016-10-21 14:58:28 -07001223 git_repo.reinit_repo_at(init_branch)
Prathmesh Prabhua637db42015-04-06 17:59:27 -07001224
Chris Sosa1bf41c42013-02-27 11:34:23 -08001225
1226 if git_repo.get_latest_commit_hash():
1227 return True
1228 return False
1229
1230
Xixuan Wuffb400a2019-03-18 13:44:26 -07001231class SuiteSchedulerRepo(_ExternalGitRepo):
1232 """Clones or updates the suite_scheduler repo."""
1233
1234 _GIT_URL = ('https://chromium.googlesource.com/chromiumos/'
1235 'infra/suite_scheduler')
1236
1237 def build_and_install(self, install_dir):
1238 """
1239 Clone if the repo isn't initialized, pull clean bits if it is.
1240
1241 @param install_dir: destination directory for suite_scheduler
1242 installation.
1243 @param master_branch: if True, install master branch. Otherwise,
1244 install prod branch.
1245 """
1246 local_dir = os.path.join(install_dir, 'suite_scheduler')
1247 git_repo = revision_control.GitRepo(
1248 local_dir,
1249 self._GIT_URL,
1250 abs_work_tree=local_dir)
1251 git_repo.reinit_repo_at(self.MASTER_BRANCH)
1252
1253 if git_repo.get_latest_commit_hash():
1254 return True
1255 return False
1256
1257
Scott James Remnantbb1a9672014-03-03 14:03:09 -08001258class BtsocketRepo(_ExternalGitRepo):
1259 """Clones or updates the btsocket repo."""
1260
1261 _GIT_URL = ('https://chromium.googlesource.com/'
1262 'chromiumos/platform/btsocket')
1263
1264 def fetch(self, unused_dest_dir):
1265 """
1266 Fetch repo to a temporary location.
1267
1268 We use an intermediate temp directory because we have to build an
1269 egg for installation. If we can't get at the top commit hash after
1270 fetching something is wrong. This can happen when we've cloned/pulled
1271 an empty repo. Not something we expect to do.
1272
1273 @parma unused_dest_dir: passed in because we inherit from
1274 ExternalPackage.
1275
1276 @return: True if repo sync was successful.
1277 """
1278 self.temp_btsocket_dir = autotemp.tempdir(unique_id='btsocket')
1279 try:
1280 git_repo = revision_control.GitRepo(
1281 self.temp_btsocket_dir.name,
1282 self._GIT_URL,
1283 None,
1284 abs_work_tree=self.temp_btsocket_dir.name)
Prathmesh Prabhu5f6b2332015-04-10 16:41:28 -07001285 git_repo.reinit_repo_at(self.PROD_BRANCH)
Scott James Remnantbb1a9672014-03-03 14:03:09 -08001286
1287 if git_repo.get_latest_commit_hash():
1288 return True
1289 except:
1290 self.temp_btsocket_dir.clean()
1291 raise
1292
1293 self.temp_btsocket_dir.clean()
1294 return False
1295
1296
1297 def build_and_install(self, install_dir):
1298 """
1299 Install the btsocket module using setup.py
1300
1301 @param install_dir: Target installation directory.
1302
1303 @return: A boolean indicating success of failure.
1304 """
1305 work_dir = os.getcwd()
1306 try:
1307 os.chdir(self.temp_btsocket_dir.name)
1308 rv = self._build_and_install_current_dir_setup_py(install_dir)
1309 finally:
1310 os.chdir(work_dir)
1311 self.temp_btsocket_dir.clean()
J. Richard Barnette428b3442014-07-22 11:38:55 -07001312 return rv
Ningning Xiac73bb7d2018-04-26 18:10:30 -07001313
1314
1315class SkylabInventoryRepo(_ExternalGitRepo):
1316 """Clones or updates the skylab_inventory repo."""
1317
1318 _GIT_URL = ('https://chromium.googlesource.com/chromiumos/infra/'
1319 'skylab_inventory')
1320
1321 # TODO(nxia): create a prod branch for skylab_inventory.
1322 def build_and_install(self, install_dir):
1323 """
1324 @param install_dir: destination directory for skylab_inventory
1325 installation.
1326 """
1327 local_skylab_dir = os.path.join(install_dir, 'infra_skylab_inventory')
1328 git_repo = revision_control.GitRepo(
1329 local_skylab_dir,
1330 self._GIT_URL,
1331 abs_work_tree=local_skylab_dir)
1332 git_repo.reinit_repo_at(self.MASTER_BRANCH)
1333
1334 # The top-level __init__.py for skylab is at venv/skylab_inventory.
Ningning Xiac6367562018-04-30 13:31:34 -07001335 source = os.path.join(local_skylab_dir, 'venv', 'skylab_inventory')
1336 link_name = os.path.join(install_dir, 'skylab_inventory')
1337
1338 if (os.path.exists(link_name) and
1339 os.path.realpath(link_name) != os.path.realpath(source)):
1340 os.remove(link_name)
1341
1342 if not os.path.exists(link_name):
1343 os.symlink(source, link_name)
Ningning Xiac73bb7d2018-04-26 18:10:30 -07001344
1345 if git_repo.get_latest_commit_hash():
1346 return True
1347 return False