xref: /aosp_15_r20/external/autotest/autotest_lib/utils/external_packages.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Please keep this code python 2.4 compatible and stand alone.
3*9c5db199SXin Li
4*9c5db199SXin Lifrom __future__ import absolute_import
5*9c5db199SXin Lifrom __future__ import division
6*9c5db199SXin Lifrom __future__ import print_function
7*9c5db199SXin Li
8*9c5db199SXin Liimport logging, os, shutil, sys, tempfile, time
9*9c5db199SXin Lifrom six.moves import urllib
10*9c5db199SXin Liimport subprocess, re
11*9c5db199SXin Lifrom distutils.version import LooseVersion
12*9c5db199SXin Li
13*9c5db199SXin Lifrom autotest_lib.client.common_lib import autotemp, revision_control, utils
14*9c5db199SXin Liimport six
15*9c5db199SXin Li
16*9c5db199SXin Li_READ_SIZE = 64*1024
17*9c5db199SXin Li_MAX_PACKAGE_SIZE = 100*1024*1024
18*9c5db199SXin Li_CHROMEOS_MIRROR = ('http://commondatastorage.googleapis.com/'
19*9c5db199SXin Li                    'chromeos-mirror/gentoo/distfiles/')
20*9c5db199SXin Li
21*9c5db199SXin Li
22*9c5db199SXin Liclass Error(Exception):
23*9c5db199SXin Li    """Local exception to be raised by code in this file."""
24*9c5db199SXin Li
25*9c5db199SXin Liclass FetchError(Error):
26*9c5db199SXin Li    """Failed to fetch a package from any of its listed URLs."""
27*9c5db199SXin Li
28*9c5db199SXin Li
29*9c5db199SXin Lidef _checksum_file(full_path):
30*9c5db199SXin Li    """@returns The hex checksum of a file given its pathname."""
31*9c5db199SXin Li    inputfile = open(full_path, 'rb')
32*9c5db199SXin Li    try:
33*9c5db199SXin Li        hex_sum = utils.hash('sha1', inputfile.read()).hexdigest()
34*9c5db199SXin Li    finally:
35*9c5db199SXin Li        inputfile.close()
36*9c5db199SXin Li    return hex_sum
37*9c5db199SXin Li
38*9c5db199SXin Li
39*9c5db199SXin Lidef system(commandline):
40*9c5db199SXin Li    """Same as os.system(commandline) but logs the command first.
41*9c5db199SXin Li
42*9c5db199SXin Li    @param commandline: commandline to be called.
43*9c5db199SXin Li    """
44*9c5db199SXin Li    logging.info(commandline)
45*9c5db199SXin Li    return os.system(commandline)
46*9c5db199SXin Li
47*9c5db199SXin Li
48*9c5db199SXin Lidef find_top_of_autotest_tree():
49*9c5db199SXin Li    """@returns The full path to the top of the autotest directory tree."""
50*9c5db199SXin Li    dirname = os.path.dirname(__file__)
51*9c5db199SXin Li    autotest_dir = os.path.abspath(os.path.join(dirname, '..'))
52*9c5db199SXin Li    return autotest_dir
53*9c5db199SXin Li
54*9c5db199SXin Li
55*9c5db199SXin Liclass ExternalPackage(object):
56*9c5db199SXin Li    """
57*9c5db199SXin Li    Defines an external package with URLs to fetch its sources from and
58*9c5db199SXin Li    a build_and_install() method to unpack it, build it and install it
59*9c5db199SXin Li    beneath our own autotest/site-packages directory.
60*9c5db199SXin Li
61*9c5db199SXin Li    Base Class.  Subclass this to define packages.
62*9c5db199SXin Li    Note: Unless your subclass has a specific reason to, it should not
63*9c5db199SXin Li    re-install the package every time build_externals is invoked, as this
64*9c5db199SXin Li    happens periodically through the scheduler. To avoid doing so the is_needed
65*9c5db199SXin Li    method needs to return an appropriate value.
66*9c5db199SXin Li
67*9c5db199SXin Li    Attributes:
68*9c5db199SXin Li      @attribute urls - A tuple of URLs to try fetching the package from.
69*9c5db199SXin Li      @attribute local_filename - A local filename to use when saving the
70*9c5db199SXin Li              fetched package.
71*9c5db199SXin Li      @attribute dist_name - The name of the Python distribution.  For example,
72*9c5db199SXin Li              the package MySQLdb is included in the distribution named
73*9c5db199SXin Li              MySQL-python.  This is generally the PyPI name.  Defaults to the
74*9c5db199SXin Li              name part of the local_filename.
75*9c5db199SXin Li      @attribute hex_sum - The hex digest (currently SHA1) of this package
76*9c5db199SXin Li              to be used to verify its contents.
77*9c5db199SXin Li      @attribute module_name - The installed python module name to be used for
78*9c5db199SXin Li              for a version check.  Defaults to the lower case class name with
79*9c5db199SXin Li              the word Package stripped off.
80*9c5db199SXin Li      @attribute extracted_package_path - The path to package directory after
81*9c5db199SXin Li              extracting.
82*9c5db199SXin Li      @attribute version - The desired minimum package version.
83*9c5db199SXin Li      @attribute os_requirements - A dictionary mapping pathname tuples on the
84*9c5db199SXin Li              the OS distribution to a likely name of a package the user
85*9c5db199SXin Li              needs to install on their system in order to get this file.
86*9c5db199SXin Li              One of the files in the tuple must exist.
87*9c5db199SXin Li      @attribute name - Read only, the printable name of the package.
88*9c5db199SXin Li      @attribute subclasses - This class attribute holds a list of all defined
89*9c5db199SXin Li              subclasses.  It is constructed dynamically using the metaclass.
90*9c5db199SXin Li    """
91*9c5db199SXin Li    # Modules that are meant to be installed in system directory, rather than
92*9c5db199SXin Li    # autotest/site-packages. These modules should be skipped if the module
93*9c5db199SXin Li    # is already installed in system directory. This prevents an older version
94*9c5db199SXin Li    # of the module from being installed in system directory.
95*9c5db199SXin Li    SYSTEM_MODULES = ['setuptools']
96*9c5db199SXin Li
97*9c5db199SXin Li    subclasses = []
98*9c5db199SXin Li    urls = ()
99*9c5db199SXin Li    local_filename = None
100*9c5db199SXin Li    dist_name = None
101*9c5db199SXin Li    hex_sum = None
102*9c5db199SXin Li    module_name = None
103*9c5db199SXin Li    version = None
104*9c5db199SXin Li    os_requirements = None
105*9c5db199SXin Li
106*9c5db199SXin Li
107*9c5db199SXin Li    class __metaclass__(type):
108*9c5db199SXin Li        """Any time a subclass is defined, add it to our list."""
109*9c5db199SXin Li        def __init__(mcs, name, bases, dict):
110*9c5db199SXin Li            if name != 'ExternalPackage' and not name.startswith('_'):
111*9c5db199SXin Li                mcs.subclasses.append(mcs)
112*9c5db199SXin Li
113*9c5db199SXin Li
114*9c5db199SXin Li    def __init__(self):
115*9c5db199SXin Li        self.verified_package = ''
116*9c5db199SXin Li        if not self.module_name:
117*9c5db199SXin Li            self.module_name = self.name.lower()
118*9c5db199SXin Li        if not self.dist_name and self.local_filename:
119*9c5db199SXin Li            self.dist_name = self.local_filename[:self.local_filename.rindex('-')]
120*9c5db199SXin Li        self.installed_version = ''
121*9c5db199SXin Li
122*9c5db199SXin Li
123*9c5db199SXin Li    @property
124*9c5db199SXin Li    def extracted_package_path(self):
125*9c5db199SXin Li        """Return the package path after extracting.
126*9c5db199SXin Li
127*9c5db199SXin Li        If the package has assigned its own extracted_package_path, use it.
128*9c5db199SXin Li        Or use part of its local_filename as the extracting path.
129*9c5db199SXin Li        """
130*9c5db199SXin Li        return self.local_filename[:-len(self._get_extension(
131*9c5db199SXin Li                self.local_filename))]
132*9c5db199SXin Li
133*9c5db199SXin Li
134*9c5db199SXin Li    @property
135*9c5db199SXin Li    def name(self):
136*9c5db199SXin Li        """Return the class name with any trailing 'Package' stripped off."""
137*9c5db199SXin Li        class_name = self.__class__.__name__
138*9c5db199SXin Li        if class_name.endswith('Package'):
139*9c5db199SXin Li            return class_name[:-len('Package')]
140*9c5db199SXin Li        return class_name
141*9c5db199SXin Li
142*9c5db199SXin Li
143*9c5db199SXin Li    def is_needed(self, install_dir):
144*9c5db199SXin Li        """
145*9c5db199SXin Li        Check to see if we need to reinstall a package. This is contingent on:
146*9c5db199SXin Li        1. Module name: If the name of the module is different from the package,
147*9c5db199SXin Li            the class that installs it needs to specify a module_name string,
148*9c5db199SXin Li            so we can try importing the module.
149*9c5db199SXin Li
150*9c5db199SXin Li        2. Installed version: If the module doesn't contain a __version__ the
151*9c5db199SXin Li            class that installs it needs to override the
152*9c5db199SXin Li            _get_installed_version_from_module method to return an appropriate
153*9c5db199SXin Li            version string.
154*9c5db199SXin Li
155*9c5db199SXin Li        3. Version/Minimum version: The class that installs the package should
156*9c5db199SXin Li            contain a version string, and an optional minimum version string.
157*9c5db199SXin Li
158*9c5db199SXin Li        4. install_dir: If the module exists in a different directory, e.g.,
159*9c5db199SXin Li            /usr/lib/python2.7/dist-packages/, the module will be forced to be
160*9c5db199SXin Li            installed in install_dir.
161*9c5db199SXin Li
162*9c5db199SXin Li        @param install_dir: install directory.
163*9c5db199SXin Li        @returns True if self.module_name needs to be built and installed.
164*9c5db199SXin Li        """
165*9c5db199SXin Li        if not self.module_name or not self.version:
166*9c5db199SXin Li            logging.warning('version and module_name required for '
167*9c5db199SXin Li                            'is_needed() check to work.')
168*9c5db199SXin Li            return True
169*9c5db199SXin Li        try:
170*9c5db199SXin Li            module = __import__(self.module_name)
171*9c5db199SXin Li        except ImportError as e:
172*9c5db199SXin Li            logging.info("%s isn't present. Will install.", self.module_name)
173*9c5db199SXin Li            return True
174*9c5db199SXin Li        # Check if we're getting a module installed somewhere else,
175*9c5db199SXin Li        # e.g. on the system.
176*9c5db199SXin Li        if self.module_name not in self.SYSTEM_MODULES:
177*9c5db199SXin Li            if (hasattr(module, '__file__')
178*9c5db199SXin Li                and not module.__file__.startswith(install_dir)):
179*9c5db199SXin Li                path = module.__file__
180*9c5db199SXin Li            elif (hasattr(module, '__path__')
181*9c5db199SXin Li                  and module.__path__
182*9c5db199SXin Li                  and not module.__path__[0].startswith(install_dir)):
183*9c5db199SXin Li                path = module.__path__[0]
184*9c5db199SXin Li            else:
185*9c5db199SXin Li                logging.warning('module %s has no __file__ or __path__',
186*9c5db199SXin Li                                self.module_name)
187*9c5db199SXin Li                return True
188*9c5db199SXin Li            logging.info(
189*9c5db199SXin Li                    'Found %s installed in %s, installing our version in %s',
190*9c5db199SXin Li                    self.module_name, path, install_dir)
191*9c5db199SXin Li            return True
192*9c5db199SXin Li        self.installed_version = self._get_installed_version_from_module(module)
193*9c5db199SXin Li        if not self.installed_version:
194*9c5db199SXin Li            return True
195*9c5db199SXin Li
196*9c5db199SXin Li        logging.info('imported %s version %s.', self.module_name,
197*9c5db199SXin Li                     self.installed_version)
198*9c5db199SXin Li        if hasattr(self, 'minimum_version'):
199*9c5db199SXin Li            return LooseVersion(self.minimum_version) > LooseVersion(
200*9c5db199SXin Li                    self.installed_version)
201*9c5db199SXin Li        else:
202*9c5db199SXin Li            return LooseVersion(self.version) > LooseVersion(
203*9c5db199SXin Li                    self.installed_version)
204*9c5db199SXin Li
205*9c5db199SXin Li
206*9c5db199SXin Li    def _get_installed_version_from_module(self, module):
207*9c5db199SXin Li        """Ask our module its version string and return it or '' if unknown."""
208*9c5db199SXin Li        try:
209*9c5db199SXin Li            return module.__version__
210*9c5db199SXin Li        except AttributeError:
211*9c5db199SXin Li            logging.error('could not get version from %s', module)
212*9c5db199SXin Li            return ''
213*9c5db199SXin Li
214*9c5db199SXin Li
215*9c5db199SXin Li    def _build_and_install(self, install_dir):
216*9c5db199SXin Li        """Subclasses MUST provide their own implementation."""
217*9c5db199SXin Li        raise NotImplementedError
218*9c5db199SXin Li
219*9c5db199SXin Li
220*9c5db199SXin Li    def _build_and_install_current_dir(self, install_dir):
221*9c5db199SXin Li        """
222*9c5db199SXin Li        Subclasses that use _build_and_install_from_package() MUST provide
223*9c5db199SXin Li        their own implementation of this method.
224*9c5db199SXin Li        """
225*9c5db199SXin Li        raise NotImplementedError
226*9c5db199SXin Li
227*9c5db199SXin Li
228*9c5db199SXin Li    def build_and_install(self, install_dir):
229*9c5db199SXin Li        """
230*9c5db199SXin Li        Builds and installs the package.  It must have been fetched already.
231*9c5db199SXin Li
232*9c5db199SXin Li        @param install_dir - The package installation directory.  If it does
233*9c5db199SXin Li            not exist it will be created.
234*9c5db199SXin Li        """
235*9c5db199SXin Li        if not self.verified_package:
236*9c5db199SXin Li            raise Error('Must call fetch() first.  - %s' % self.name)
237*9c5db199SXin Li        self._check_os_requirements()
238*9c5db199SXin Li        return self._build_and_install(install_dir)
239*9c5db199SXin Li
240*9c5db199SXin Li
241*9c5db199SXin Li    def _check_os_requirements(self):
242*9c5db199SXin Li        if not self.os_requirements:
243*9c5db199SXin Li            return
244*9c5db199SXin Li        failed = False
245*9c5db199SXin Li        for file_names, package_name in six.iteritems(self.os_requirements):
246*9c5db199SXin Li            if not any(os.path.exists(file_name) for file_name in file_names):
247*9c5db199SXin Li                failed = True
248*9c5db199SXin Li                logging.error('Can\'t find %s, %s probably needs it.',
249*9c5db199SXin Li                              ' or '.join(file_names), self.name)
250*9c5db199SXin Li                logging.error('Perhaps you need to install something similar '
251*9c5db199SXin Li                              'to the %s package for OS first.', package_name)
252*9c5db199SXin Li        if failed:
253*9c5db199SXin Li            raise Error('Missing OS requirements for %s.  (see above)' %
254*9c5db199SXin Li                        self.name)
255*9c5db199SXin Li
256*9c5db199SXin Li
257*9c5db199SXin Li    def _build_and_install_current_dir_setup_py(self, install_dir):
258*9c5db199SXin Li        """For use as a _build_and_install_current_dir implementation."""
259*9c5db199SXin Li        egg_path = self._build_egg_using_setup_py(setup_py='setup.py')
260*9c5db199SXin Li        if not egg_path:
261*9c5db199SXin Li            return False
262*9c5db199SXin Li        return self._install_from_egg(install_dir, egg_path)
263*9c5db199SXin Li
264*9c5db199SXin Li
265*9c5db199SXin Li    def _build_and_install_current_dir_setupegg_py(self, install_dir):
266*9c5db199SXin Li        """For use as a _build_and_install_current_dir implementation."""
267*9c5db199SXin Li        egg_path = self._build_egg_using_setup_py(setup_py='setupegg.py')
268*9c5db199SXin Li        if not egg_path:
269*9c5db199SXin Li            return False
270*9c5db199SXin Li        return self._install_from_egg(install_dir, egg_path)
271*9c5db199SXin Li
272*9c5db199SXin Li
273*9c5db199SXin Li    def _build_and_install_current_dir_noegg(self, install_dir):
274*9c5db199SXin Li        if not self._build_using_setup_py():
275*9c5db199SXin Li            return False
276*9c5db199SXin Li        return self._install_using_setup_py_and_rsync(install_dir)
277*9c5db199SXin Li
278*9c5db199SXin Li
279*9c5db199SXin Li    def _get_extension(self, package):
280*9c5db199SXin Li        """Get extension of package."""
281*9c5db199SXin Li        valid_package_extensions = ['.tar.gz', '.tar.bz2', '.zip']
282*9c5db199SXin Li        extension = None
283*9c5db199SXin Li
284*9c5db199SXin Li        for ext in valid_package_extensions:
285*9c5db199SXin Li            if package.endswith(ext):
286*9c5db199SXin Li                extension = ext
287*9c5db199SXin Li                break
288*9c5db199SXin Li
289*9c5db199SXin Li        if not extension:
290*9c5db199SXin Li            raise Error('Unexpected package file extension on %s' % package)
291*9c5db199SXin Li
292*9c5db199SXin Li        return extension
293*9c5db199SXin Li
294*9c5db199SXin Li
295*9c5db199SXin Li    def _build_and_install_from_package(self, install_dir):
296*9c5db199SXin Li        """
297*9c5db199SXin Li        This method may be used as a _build_and_install() implementation
298*9c5db199SXin Li        for subclasses if they implement _build_and_install_current_dir().
299*9c5db199SXin Li
300*9c5db199SXin Li        Extracts the .tar.gz file, chdirs into the extracted directory
301*9c5db199SXin Li        (which is assumed to match the tar filename) and calls
302*9c5db199SXin Li        _build_and_isntall_current_dir from there.
303*9c5db199SXin Li
304*9c5db199SXin Li        Afterwards the build (regardless of failure) extracted .tar.gz
305*9c5db199SXin Li        directory is cleaned up.
306*9c5db199SXin Li
307*9c5db199SXin Li        @returns True on success, False otherwise.
308*9c5db199SXin Li
309*9c5db199SXin Li        @raises OSError If the expected extraction directory does not exist.
310*9c5db199SXin Li        """
311*9c5db199SXin Li        self._extract_compressed_package()
312*9c5db199SXin Li        extension = self._get_extension(self.verified_package)
313*9c5db199SXin Li        os.chdir(os.path.dirname(self.verified_package))
314*9c5db199SXin Li        os.chdir(self.extracted_package_path)
315*9c5db199SXin Li        extracted_dir = os.getcwd()
316*9c5db199SXin Li        try:
317*9c5db199SXin Li            return self._build_and_install_current_dir(install_dir)
318*9c5db199SXin Li        finally:
319*9c5db199SXin Li            os.chdir(os.path.join(extracted_dir, '..'))
320*9c5db199SXin Li            shutil.rmtree(extracted_dir)
321*9c5db199SXin Li
322*9c5db199SXin Li
323*9c5db199SXin Li    def _extract_compressed_package(self):
324*9c5db199SXin Li        """Extract the fetched compressed .tar or .zip within its directory."""
325*9c5db199SXin Li        if not self.verified_package:
326*9c5db199SXin Li            raise Error('Package must have been fetched first.')
327*9c5db199SXin Li        os.chdir(os.path.dirname(self.verified_package))
328*9c5db199SXin Li        if self.verified_package.endswith('gz'):
329*9c5db199SXin Li            status = system("tar -xzf '%s'" % self.verified_package)
330*9c5db199SXin Li        elif self.verified_package.endswith('bz2'):
331*9c5db199SXin Li            status = system("tar -xjf '%s'" % self.verified_package)
332*9c5db199SXin Li        elif self.verified_package.endswith('zip'):
333*9c5db199SXin Li            status = system("unzip '%s'" % self.verified_package)
334*9c5db199SXin Li        else:
335*9c5db199SXin Li            raise Error('Unknown compression suffix on %s.' %
336*9c5db199SXin Li                        self.verified_package)
337*9c5db199SXin Li        if status:
338*9c5db199SXin Li            raise Error('tar failed with %s' % (status,))
339*9c5db199SXin Li
340*9c5db199SXin Li
341*9c5db199SXin Li    def _build_using_setup_py(self, setup_py='setup.py'):
342*9c5db199SXin Li        """
343*9c5db199SXin Li        Assuming the cwd is the extracted python package, execute a simple
344*9c5db199SXin Li        python setup.py build.
345*9c5db199SXin Li
346*9c5db199SXin Li        @param setup_py - The name of the setup.py file to execute.
347*9c5db199SXin Li
348*9c5db199SXin Li        @returns True on success, False otherwise.
349*9c5db199SXin Li        """
350*9c5db199SXin Li        if not os.path.exists(setup_py):
351*9c5db199SXin Li            raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
352*9c5db199SXin Li        status = system("'%s' %s build" % (sys.executable, setup_py))
353*9c5db199SXin Li        if status:
354*9c5db199SXin Li            logging.error('%s build failed.', self.name)
355*9c5db199SXin Li            return False
356*9c5db199SXin Li        return True
357*9c5db199SXin Li
358*9c5db199SXin Li
359*9c5db199SXin Li    def _build_egg_using_setup_py(self, setup_py='setup.py'):
360*9c5db199SXin Li        """
361*9c5db199SXin Li        Assuming the cwd is the extracted python package, execute a simple
362*9c5db199SXin Li        python setup.py bdist_egg.
363*9c5db199SXin Li
364*9c5db199SXin Li        @param setup_py - The name of the setup.py file to execute.
365*9c5db199SXin Li
366*9c5db199SXin Li        @returns The relative path to the resulting egg file or '' on failure.
367*9c5db199SXin Li        """
368*9c5db199SXin Li        if not os.path.exists(setup_py):
369*9c5db199SXin Li            raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
370*9c5db199SXin Li        egg_subdir = 'dist'
371*9c5db199SXin Li        if os.path.isdir(egg_subdir):
372*9c5db199SXin Li            shutil.rmtree(egg_subdir)
373*9c5db199SXin Li        status = system("'%s' %s bdist_egg" % (sys.executable, setup_py))
374*9c5db199SXin Li        if status:
375*9c5db199SXin Li            logging.error('bdist_egg of setuptools failed.')
376*9c5db199SXin Li            return ''
377*9c5db199SXin Li        # I've never seen a bdist_egg lay multiple .egg files.
378*9c5db199SXin Li        for filename in os.listdir(egg_subdir):
379*9c5db199SXin Li            if filename.endswith('.egg'):
380*9c5db199SXin Li                return os.path.join(egg_subdir, filename)
381*9c5db199SXin Li
382*9c5db199SXin Li
383*9c5db199SXin Li    def _install_from_egg(self, install_dir, egg_path):
384*9c5db199SXin Li        """
385*9c5db199SXin Li        Install a module from an egg file by unzipping the necessary parts
386*9c5db199SXin Li        into install_dir.
387*9c5db199SXin Li
388*9c5db199SXin Li        @param install_dir - The installation directory.
389*9c5db199SXin Li        @param egg_path - The pathname of the egg file.
390*9c5db199SXin Li        """
391*9c5db199SXin Li        status = system("unzip -q -o -d '%s' '%s'" % (install_dir, egg_path))
392*9c5db199SXin Li        if status:
393*9c5db199SXin Li            logging.error('unzip of %s failed', egg_path)
394*9c5db199SXin Li            return False
395*9c5db199SXin Li        egg_info_dir = os.path.join(install_dir, 'EGG-INFO')
396*9c5db199SXin Li        if os.path.isdir(egg_info_dir):
397*9c5db199SXin Li            egg_info_new_path = self._get_egg_info_path(install_dir)
398*9c5db199SXin Li            if egg_info_new_path:
399*9c5db199SXin Li                if os.path.exists(egg_info_new_path):
400*9c5db199SXin Li                    shutil.rmtree(egg_info_new_path)
401*9c5db199SXin Li                os.rename(egg_info_dir, egg_info_new_path)
402*9c5db199SXin Li            else:
403*9c5db199SXin Li                shutil.rmtree(egg_info_dir)
404*9c5db199SXin Li        return True
405*9c5db199SXin Li
406*9c5db199SXin Li
407*9c5db199SXin Li    def _get_egg_info_path(self, install_dir):
408*9c5db199SXin Li        """Get egg-info path for this package.
409*9c5db199SXin Li
410*9c5db199SXin Li        Example path: install_dir/MySQL_python-1.2.3.egg-info
411*9c5db199SXin Li
412*9c5db199SXin Li        """
413*9c5db199SXin Li        if self.dist_name:
414*9c5db199SXin Li            egg_info_name_part = self.dist_name.replace('-', '_')
415*9c5db199SXin Li            if self.version:
416*9c5db199SXin Li                egg_info_filename = '%s-%s.egg-info' % (egg_info_name_part,
417*9c5db199SXin Li                                                        self.version)
418*9c5db199SXin Li            else:
419*9c5db199SXin Li                egg_info_filename = '%s.egg-info' % (egg_info_name_part,)
420*9c5db199SXin Li            return os.path.join(install_dir, egg_info_filename)
421*9c5db199SXin Li        else:
422*9c5db199SXin Li            return None
423*9c5db199SXin Li
424*9c5db199SXin Li
425*9c5db199SXin Li    def _get_temp_dir(self):
426*9c5db199SXin Li        return tempfile.mkdtemp(dir='/var/tmp')
427*9c5db199SXin Li
428*9c5db199SXin Li
429*9c5db199SXin Li    def _site_packages_path(self, temp_dir):
430*9c5db199SXin Li        # This makes assumptions about what python setup.py install
431*9c5db199SXin Li        # does when given a prefix.  Is this always correct?
432*9c5db199SXin Li        python_xy = 'python%s' % sys.version[:3]
433*9c5db199SXin Li        return os.path.join(temp_dir, 'lib', python_xy, 'site-packages')
434*9c5db199SXin Li
435*9c5db199SXin Li
436*9c5db199SXin Li    def _rsync (self, temp_site_dir, install_dir):
437*9c5db199SXin Li        """Rsync contents. """
438*9c5db199SXin Li        status = system("rsync -r '%s/' '%s/'" %
439*9c5db199SXin Li                        (os.path.normpath(temp_site_dir),
440*9c5db199SXin Li                         os.path.normpath(install_dir)))
441*9c5db199SXin Li        if status:
442*9c5db199SXin Li            logging.error('%s rsync to install_dir failed.', self.name)
443*9c5db199SXin Li            return False
444*9c5db199SXin Li        return True
445*9c5db199SXin Li
446*9c5db199SXin Li
447*9c5db199SXin Li    def _install_using_setup_py_and_rsync(self, install_dir,
448*9c5db199SXin Li                                          setup_py='setup.py',
449*9c5db199SXin Li                                          temp_dir=None):
450*9c5db199SXin Li        """
451*9c5db199SXin Li        Assuming the cwd is the extracted python package, execute a simple:
452*9c5db199SXin Li
453*9c5db199SXin Li          python setup.py install --prefix=BLA
454*9c5db199SXin Li
455*9c5db199SXin Li        BLA will be a temporary directory that everything installed will
456*9c5db199SXin Li        be picked out of and rsynced to the appropriate place under
457*9c5db199SXin Li        install_dir afterwards.
458*9c5db199SXin Li
459*9c5db199SXin Li        Afterwards, it deconstructs the extra lib/pythonX.Y/site-packages/
460*9c5db199SXin Li        directory tree that setuptools created and moves all installed
461*9c5db199SXin Li        site-packages directly up into install_dir itself.
462*9c5db199SXin Li
463*9c5db199SXin Li        @param install_dir the directory for the install to happen under.
464*9c5db199SXin Li        @param setup_py - The name of the setup.py file to execute.
465*9c5db199SXin Li
466*9c5db199SXin Li        @returns True on success, False otherwise.
467*9c5db199SXin Li        """
468*9c5db199SXin Li        if not os.path.exists(setup_py):
469*9c5db199SXin Li            raise Error('%s does not exist in %s' % (setup_py, os.getcwd()))
470*9c5db199SXin Li
471*9c5db199SXin Li        if temp_dir is None:
472*9c5db199SXin Li            temp_dir = self._get_temp_dir()
473*9c5db199SXin Li
474*9c5db199SXin Li        try:
475*9c5db199SXin Li            status = system("'%s' %s install --no-compile --prefix='%s'"
476*9c5db199SXin Li                            % (sys.executable, setup_py, temp_dir))
477*9c5db199SXin Li            if status:
478*9c5db199SXin Li                logging.error('%s install failed.', self.name)
479*9c5db199SXin Li                return False
480*9c5db199SXin Li
481*9c5db199SXin Li            if os.path.isdir(os.path.join(temp_dir, 'lib')):
482*9c5db199SXin Li                # NOTE: This ignores anything outside of the lib/ dir that
483*9c5db199SXin Li                # was installed.
484*9c5db199SXin Li                temp_site_dir = self._site_packages_path(temp_dir)
485*9c5db199SXin Li            else:
486*9c5db199SXin Li                temp_site_dir = temp_dir
487*9c5db199SXin Li
488*9c5db199SXin Li            return self._rsync(temp_site_dir, install_dir)
489*9c5db199SXin Li        finally:
490*9c5db199SXin Li            shutil.rmtree(temp_dir)
491*9c5db199SXin Li
492*9c5db199SXin Li
493*9c5db199SXin Li
494*9c5db199SXin Li    def _build_using_make(self, install_dir):
495*9c5db199SXin Li        """Build the current package using configure/make.
496*9c5db199SXin Li
497*9c5db199SXin Li        @returns True on success, False otherwise.
498*9c5db199SXin Li        """
499*9c5db199SXin Li        install_prefix = os.path.join(install_dir, 'usr', 'local')
500*9c5db199SXin Li        status = system('./configure --prefix=%s' % install_prefix)
501*9c5db199SXin Li        if status:
502*9c5db199SXin Li            logging.error('./configure failed for %s', self.name)
503*9c5db199SXin Li            return False
504*9c5db199SXin Li        status = system('make')
505*9c5db199SXin Li        if status:
506*9c5db199SXin Li            logging.error('make failed for %s', self.name)
507*9c5db199SXin Li            return False
508*9c5db199SXin Li        status = system('make check')
509*9c5db199SXin Li        if status:
510*9c5db199SXin Li            logging.error('make check failed for %s', self.name)
511*9c5db199SXin Li            return False
512*9c5db199SXin Li        return True
513*9c5db199SXin Li
514*9c5db199SXin Li
515*9c5db199SXin Li    def _install_using_make(self):
516*9c5db199SXin Li        """Install the current package using make install.
517*9c5db199SXin Li
518*9c5db199SXin Li        Assumes the install path was set up while running ./configure (in
519*9c5db199SXin Li        _build_using_make()).
520*9c5db199SXin Li
521*9c5db199SXin Li        @returns True on success, False otherwise.
522*9c5db199SXin Li        """
523*9c5db199SXin Li        status = system('make install')
524*9c5db199SXin Li        return status == 0
525*9c5db199SXin Li
526*9c5db199SXin Li
527*9c5db199SXin Li    def fetch(self, dest_dir):
528*9c5db199SXin Li        """
529*9c5db199SXin Li        Fetch the package from one its URLs and save it in dest_dir.
530*9c5db199SXin Li
531*9c5db199SXin Li        If the the package already exists in dest_dir and the checksum
532*9c5db199SXin Li        matches this code will not fetch it again.
533*9c5db199SXin Li
534*9c5db199SXin Li        Sets the 'verified_package' attribute with the destination pathname.
535*9c5db199SXin Li
536*9c5db199SXin Li        @param dest_dir - The destination directory to save the local file.
537*9c5db199SXin Li            If it does not exist it will be created.
538*9c5db199SXin Li
539*9c5db199SXin Li        @returns A boolean indicating if we the package is now in dest_dir.
540*9c5db199SXin Li        @raises FetchError - When something unexpected happens.
541*9c5db199SXin Li        """
542*9c5db199SXin Li        if not os.path.exists(dest_dir):
543*9c5db199SXin Li            os.makedirs(dest_dir)
544*9c5db199SXin Li        local_path = os.path.join(dest_dir, self.local_filename)
545*9c5db199SXin Li
546*9c5db199SXin Li        # If the package exists, verify its checksum and be happy if it is good.
547*9c5db199SXin Li        if os.path.exists(local_path):
548*9c5db199SXin Li            actual_hex_sum = _checksum_file(local_path)
549*9c5db199SXin Li            if self.hex_sum == actual_hex_sum:
550*9c5db199SXin Li                logging.info('Good checksum for existing %s package.',
551*9c5db199SXin Li                             self.name)
552*9c5db199SXin Li                self.verified_package = local_path
553*9c5db199SXin Li                return True
554*9c5db199SXin Li            logging.warning('Bad checksum for existing %s package.  '
555*9c5db199SXin Li                            'Re-downloading', self.name)
556*9c5db199SXin Li            os.rename(local_path, local_path + '.wrong-checksum')
557*9c5db199SXin Li
558*9c5db199SXin Li        # Download the package from one of its urls, rejecting any if the
559*9c5db199SXin Li        # checksum does not match.
560*9c5db199SXin Li        for url in self.urls:
561*9c5db199SXin Li            logging.info('Fetching %s', url)
562*9c5db199SXin Li            try:
563*9c5db199SXin Li                url_file = urllib.request.urlopen(url)
564*9c5db199SXin Li            except (urllib.error.URLError, EnvironmentError):
565*9c5db199SXin Li                logging.warning('Could not fetch %s package from %s.',
566*9c5db199SXin Li                                self.name, url)
567*9c5db199SXin Li                continue
568*9c5db199SXin Li
569*9c5db199SXin Li            data_length = int(url_file.info().get('Content-Length',
570*9c5db199SXin Li                                                  _MAX_PACKAGE_SIZE))
571*9c5db199SXin Li            if data_length <= 0 or data_length > _MAX_PACKAGE_SIZE:
572*9c5db199SXin Li                raise FetchError('%s from %s fails Content-Length %d '
573*9c5db199SXin Li                                 'validity check.' % (self.name, url,
574*9c5db199SXin Li                                                    data_length))
575*9c5db199SXin Li            checksum = utils.hash('sha1')
576*9c5db199SXin Li            total_read = 0
577*9c5db199SXin Li            output = open(local_path, 'wb')
578*9c5db199SXin Li            try:
579*9c5db199SXin Li                while total_read < data_length:
580*9c5db199SXin Li                    data = url_file.read(_READ_SIZE)
581*9c5db199SXin Li                    if not data:
582*9c5db199SXin Li                        break
583*9c5db199SXin Li                    output.write(data)
584*9c5db199SXin Li                    checksum.update(data)
585*9c5db199SXin Li                    total_read += len(data)
586*9c5db199SXin Li            finally:
587*9c5db199SXin Li                output.close()
588*9c5db199SXin Li            if self.hex_sum != checksum.hexdigest():
589*9c5db199SXin Li                logging.warning('Bad checksum for %s fetched from %s.',
590*9c5db199SXin Li                                self.name, url)
591*9c5db199SXin Li                logging.warning('Got %s', checksum.hexdigest())
592*9c5db199SXin Li                logging.warning('Expected %s', self.hex_sum)
593*9c5db199SXin Li                os.unlink(local_path)
594*9c5db199SXin Li                continue
595*9c5db199SXin Li            logging.info('Good checksum.')
596*9c5db199SXin Li            self.verified_package = local_path
597*9c5db199SXin Li            return True
598*9c5db199SXin Li        else:
599*9c5db199SXin Li            return False
600*9c5db199SXin Li
601*9c5db199SXin Li
602*9c5db199SXin Li# NOTE: This class definition must come -before- all other ExternalPackage
603*9c5db199SXin Li# classes that need to use this version of setuptools so that is is inserted
604*9c5db199SXin Li# into the ExternalPackage.subclasses list before them.
605*9c5db199SXin Liclass SetuptoolsPackage(ExternalPackage):
606*9c5db199SXin Li    """setuptools package"""
607*9c5db199SXin Li    # For all known setuptools releases a string compare works for the
608*9c5db199SXin Li    # version string.  Hopefully they never release a 0.10.  (Their own
609*9c5db199SXin Li    # version comparison code would break if they did.)
610*9c5db199SXin Li    # Any system with setuptools > 18.0.1 is fine. If none installed, then
611*9c5db199SXin Li    # try to install the latest found on the upstream.
612*9c5db199SXin Li    minimum_version = '18.0.1'
613*9c5db199SXin Li    version = '18.0.1'
614*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + 'setuptools-%s.tar.gz' % (version,),)
615*9c5db199SXin Li    local_filename = 'setuptools-%s.tar.gz' % version
616*9c5db199SXin Li    hex_sum = 'ebc4fe81b7f6d61d923d9519f589903824044f52'
617*9c5db199SXin Li
618*9c5db199SXin Li    SUDO_SLEEP_DELAY = 15
619*9c5db199SXin Li
620*9c5db199SXin Li
621*9c5db199SXin Li    def _build_and_install(self, install_dir):
622*9c5db199SXin Li        """Install setuptools on the system."""
623*9c5db199SXin Li        logging.info('NOTE: setuptools install does not use install_dir.')
624*9c5db199SXin Li        return self._build_and_install_from_package(install_dir)
625*9c5db199SXin Li
626*9c5db199SXin Li
627*9c5db199SXin Li    def _build_and_install_current_dir(self, install_dir):
628*9c5db199SXin Li        egg_path = self._build_egg_using_setup_py()
629*9c5db199SXin Li        if not egg_path:
630*9c5db199SXin Li            return False
631*9c5db199SXin Li
632*9c5db199SXin Li        print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n')
633*9c5db199SXin Li        print('About to run sudo to install setuptools', self.version)
634*9c5db199SXin Li        print('on your system for use by', sys.executable, '\n')
635*9c5db199SXin Li        print('!! ^C within', self.SUDO_SLEEP_DELAY, 'seconds to abort.\n')
636*9c5db199SXin Li        time.sleep(self.SUDO_SLEEP_DELAY)
637*9c5db199SXin Li
638*9c5db199SXin Li        # Copy the egg to the local filesystem /var/tmp so that root can
639*9c5db199SXin Li        # access it properly (avoid NFS squashroot issues).
640*9c5db199SXin Li        temp_dir = self._get_temp_dir()
641*9c5db199SXin Li        try:
642*9c5db199SXin Li            shutil.copy(egg_path, temp_dir)
643*9c5db199SXin Li            egg_name = os.path.split(egg_path)[1]
644*9c5db199SXin Li            temp_egg = os.path.join(temp_dir, egg_name)
645*9c5db199SXin Li            p = subprocess.Popen(['sudo', '/bin/sh', temp_egg],
646*9c5db199SXin Li                                 stdout=subprocess.PIPE)
647*9c5db199SXin Li            regex = re.compile('Copying (.*?) to (.*?)\n')
648*9c5db199SXin Li            match = regex.search(p.communicate()[0].decode('utf-8'))
649*9c5db199SXin Li            status = p.wait()
650*9c5db199SXin Li
651*9c5db199SXin Li            if match:
652*9c5db199SXin Li                compiled = os.path.join(match.group(2), match.group(1))
653*9c5db199SXin Li                os.system("sudo chmod a+r '%s'" % compiled)
654*9c5db199SXin Li        finally:
655*9c5db199SXin Li            shutil.rmtree(temp_dir)
656*9c5db199SXin Li
657*9c5db199SXin Li        if status:
658*9c5db199SXin Li            logging.error('install of setuptools from egg failed.')
659*9c5db199SXin Li            return False
660*9c5db199SXin Li        return True
661*9c5db199SXin Li
662*9c5db199SXin Li
663*9c5db199SXin Liclass MySQLdbPackage(ExternalPackage):
664*9c5db199SXin Li    """mysql package, used in scheduler."""
665*9c5db199SXin Li    module_name = 'MySQLdb'
666*9c5db199SXin Li    version = '1.2.3'
667*9c5db199SXin Li    local_filename = 'MySQL-python-%s.tar.gz' % version
668*9c5db199SXin Li    urls = ('http://commondatastorage.googleapis.com/chromeos-mirror/gentoo/'
669*9c5db199SXin Li            'distfiles/%s' % local_filename,)
670*9c5db199SXin Li    hex_sum = '3511bb8c57c6016eeafa531d5c3ea4b548915e3c'
671*9c5db199SXin Li
672*9c5db199SXin Li    _build_and_install_current_dir = (
673*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
674*9c5db199SXin Li
675*9c5db199SXin Li
676*9c5db199SXin Li    def _build_and_install(self, install_dir):
677*9c5db199SXin Li        if not os.path.exists('/usr/bin/mysql_config'):
678*9c5db199SXin Li            error_msg = '''\
679*9c5db199SXin LiYou need to install /usr/bin/mysql_config.
680*9c5db199SXin LiOn recent Debian based distros, run: \
681*9c5db199SXin Lisudo apt-get install libmariadbclient-dev-compat
682*9c5db199SXin LiOn older Debian based distros, run: sudo apt-get install libmysqlclient15-dev
683*9c5db199SXin Li'''
684*9c5db199SXin Li            logging.error(error_msg)
685*9c5db199SXin Li            return False, error_msg
686*9c5db199SXin Li        return self._build_and_install_from_package(install_dir)
687*9c5db199SXin Li
688*9c5db199SXin Li
689*9c5db199SXin Liclass DjangoPackage(ExternalPackage):
690*9c5db199SXin Li    """django package."""
691*9c5db199SXin Li    version = '1.5.1'
692*9c5db199SXin Li    local_filename = 'Django-%s.tar.gz' % version
693*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
694*9c5db199SXin Li    hex_sum = '0ab97b90c4c79636e56337f426f1e875faccbba1'
695*9c5db199SXin Li
696*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
697*9c5db199SXin Li    _build_and_install_current_dir = (
698*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_noegg)
699*9c5db199SXin Li
700*9c5db199SXin Li
701*9c5db199SXin Li    def _get_installed_version_from_module(self, module):
702*9c5db199SXin Li        try:
703*9c5db199SXin Li            return module.get_version().split()[0]
704*9c5db199SXin Li        except AttributeError:
705*9c5db199SXin Li            return '0.9.6'
706*9c5db199SXin Li
707*9c5db199SXin Li
708*9c5db199SXin Li
709*9c5db199SXin Liclass NumpyPackage(ExternalPackage):
710*9c5db199SXin Li    """numpy package, required by matploglib."""
711*9c5db199SXin Li    version = '1.7.0'
712*9c5db199SXin Li    local_filename = 'numpy-%s.tar.gz' % version
713*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
714*9c5db199SXin Li    hex_sum = 'ba328985f20390b0f969a5be2a6e1141d5752cf9'
715*9c5db199SXin Li
716*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
717*9c5db199SXin Li    _build_and_install_current_dir = (
718*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setupegg_py)
719*9c5db199SXin Li
720*9c5db199SXin Li
721*9c5db199SXin Liclass GwtPackage(ExternalPackage):
722*9c5db199SXin Li    """Fetch and extract a local copy of GWT used to build the frontend."""
723*9c5db199SXin Li
724*9c5db199SXin Li    version = '2.3.0'
725*9c5db199SXin Li    local_filename = 'gwt-%s.zip' % version
726*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
727*9c5db199SXin Li    hex_sum = 'd51fce9166e6b31349659ffca89baf93e39bc84b'
728*9c5db199SXin Li    name = 'gwt'
729*9c5db199SXin Li    about_filename = 'about.txt'
730*9c5db199SXin Li    module_name = None  # Not a Python module.
731*9c5db199SXin Li
732*9c5db199SXin Li
733*9c5db199SXin Li    def is_needed(self, install_dir):
734*9c5db199SXin Li        gwt_dir = os.path.join(install_dir, self.name)
735*9c5db199SXin Li        about_file = os.path.join(install_dir, self.name, self.about_filename)
736*9c5db199SXin Li
737*9c5db199SXin Li        if not os.path.exists(gwt_dir) or not os.path.exists(about_file):
738*9c5db199SXin Li            logging.info('gwt not installed for autotest')
739*9c5db199SXin Li            return True
740*9c5db199SXin Li
741*9c5db199SXin Li        f = open(about_file, 'r')
742*9c5db199SXin Li        version_line = f.readline()
743*9c5db199SXin Li        f.close()
744*9c5db199SXin Li
745*9c5db199SXin Li        match = re.match(r'Google Web Toolkit (.*)', version_line)
746*9c5db199SXin Li        if not match:
747*9c5db199SXin Li            logging.info('did not find gwt version')
748*9c5db199SXin Li            return True
749*9c5db199SXin Li
750*9c5db199SXin Li        logging.info('found gwt version %s', match.group(1))
751*9c5db199SXin Li        return match.group(1) != self.version
752*9c5db199SXin Li
753*9c5db199SXin Li
754*9c5db199SXin Li    def _build_and_install(self, install_dir):
755*9c5db199SXin Li        os.chdir(install_dir)
756*9c5db199SXin Li        self._extract_compressed_package()
757*9c5db199SXin Li        extracted_dir = self.local_filename[:-len('.zip')]
758*9c5db199SXin Li        target_dir = os.path.join(install_dir, self.name)
759*9c5db199SXin Li        if os.path.exists(target_dir):
760*9c5db199SXin Li            shutil.rmtree(target_dir)
761*9c5db199SXin Li        os.rename(extracted_dir, target_dir)
762*9c5db199SXin Li        return True
763*9c5db199SXin Li
764*9c5db199SXin Li
765*9c5db199SXin Liclass PyudevPackage(ExternalPackage):
766*9c5db199SXin Li    """
767*9c5db199SXin Li    pyudev module
768*9c5db199SXin Li
769*9c5db199SXin Li    Used in unittests.
770*9c5db199SXin Li    """
771*9c5db199SXin Li    version = '0.16.1'
772*9c5db199SXin Li    url_filename = 'pyudev-%s.tar.gz' % version
773*9c5db199SXin Li    local_filename = url_filename
774*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
775*9c5db199SXin Li    hex_sum = 'b36bc5c553ce9b56d32a5e45063a2c88156771c0'
776*9c5db199SXin Li
777*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
778*9c5db199SXin Li    _build_and_install_current_dir = (
779*9c5db199SXin Li                        ExternalPackage._build_and_install_current_dir_setup_py)
780*9c5db199SXin Li
781*9c5db199SXin Li
782*9c5db199SXin Liclass PyMoxPackage(ExternalPackage):
783*9c5db199SXin Li    """
784*9c5db199SXin Li    mox module
785*9c5db199SXin Li
786*9c5db199SXin Li    Used in unittests.
787*9c5db199SXin Li    """
788*9c5db199SXin Li    module_name = 'mox'
789*9c5db199SXin Li    version = '0.5.3'
790*9c5db199SXin Li    # Note: url_filename does not match local_filename, because of
791*9c5db199SXin Li    # an uncontrolled fork at some point in time of mox versions.
792*9c5db199SXin Li    url_filename = 'mox-%s-autotest.tar.gz' % version
793*9c5db199SXin Li    local_filename = 'mox-%s.tar.gz' % version
794*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + url_filename,)
795*9c5db199SXin Li    hex_sum = '1c502d2c0a8aefbba2c7f385a83d33e7d822452a'
796*9c5db199SXin Li
797*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
798*9c5db199SXin Li    _build_and_install_current_dir = (
799*9c5db199SXin Li                        ExternalPackage._build_and_install_current_dir_noegg)
800*9c5db199SXin Li
801*9c5db199SXin Li    def _get_installed_version_from_module(self, module):
802*9c5db199SXin Li        # mox doesn't contain a proper version
803*9c5db199SXin Li        return self.version
804*9c5db199SXin Li
805*9c5db199SXin Li
806*9c5db199SXin Liclass PySeleniumPackage(ExternalPackage):
807*9c5db199SXin Li    """
808*9c5db199SXin Li    selenium module
809*9c5db199SXin Li
810*9c5db199SXin Li    Used in wifi_interop suite.
811*9c5db199SXin Li    """
812*9c5db199SXin Li    module_name = 'selenium'
813*9c5db199SXin Li    version = '2.37.2'
814*9c5db199SXin Li    url_filename = 'selenium-%s.tar.gz' % version
815*9c5db199SXin Li    local_filename = url_filename
816*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
817*9c5db199SXin Li    hex_sum = '66946d5349e36d946daaad625c83c30c11609e36'
818*9c5db199SXin Li
819*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
820*9c5db199SXin Li    _build_and_install_current_dir = (
821*9c5db199SXin Li                        ExternalPackage._build_and_install_current_dir_setup_py)
822*9c5db199SXin Li
823*9c5db199SXin Li
824*9c5db199SXin Liclass FaultHandlerPackage(ExternalPackage):
825*9c5db199SXin Li    """
826*9c5db199SXin Li    faulthandler module
827*9c5db199SXin Li    """
828*9c5db199SXin Li    module_name = 'faulthandler'
829*9c5db199SXin Li    version = '2.3'
830*9c5db199SXin Li    url_filename = '%s-%s.tar.gz' % (module_name, version)
831*9c5db199SXin Li    local_filename = url_filename
832*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
833*9c5db199SXin Li    hex_sum = 'efb30c068414fba9df892e48fcf86170cbf53589'
834*9c5db199SXin Li
835*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
836*9c5db199SXin Li    _build_and_install_current_dir = (
837*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_noegg)
838*9c5db199SXin Li
839*9c5db199SXin Li
840*9c5db199SXin Liclass PsutilPackage(ExternalPackage):
841*9c5db199SXin Li    """
842*9c5db199SXin Li    psutil module
843*9c5db199SXin Li    """
844*9c5db199SXin Li    module_name = 'psutil'
845*9c5db199SXin Li    version = '2.1.1'
846*9c5db199SXin Li    url_filename = '%s-%s.tar.gz' % (module_name, version)
847*9c5db199SXin Li    local_filename = url_filename
848*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
849*9c5db199SXin Li    hex_sum = '0c20a20ed316e69f2b0881530439213988229916'
850*9c5db199SXin Li
851*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
852*9c5db199SXin Li    _build_and_install_current_dir = (
853*9c5db199SXin Li                        ExternalPackage._build_and_install_current_dir_setup_py)
854*9c5db199SXin Li
855*9c5db199SXin Li
856*9c5db199SXin Liclass Urllib3Package(ExternalPackage):
857*9c5db199SXin Li    """elasticsearch-py package."""
858*9c5db199SXin Li    version = '1.23'
859*9c5db199SXin Li    url_filename = 'urllib3-%s.tar.gz' % version
860*9c5db199SXin Li    local_filename = url_filename
861*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
862*9c5db199SXin Li    hex_sum = '0c54209c397958a7cebe13cb453ec8ef5833998d'
863*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
864*9c5db199SXin Li    _build_and_install_current_dir = (
865*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
866*9c5db199SXin Li
867*9c5db199SXin Liclass ImagingLibraryPackage(ExternalPackage):
868*9c5db199SXin Li     """Python Imaging Library (PIL)."""
869*9c5db199SXin Li     version = '1.1.7'
870*9c5db199SXin Li     url_filename = 'Imaging-%s.tar.gz' % version
871*9c5db199SXin Li     local_filename = url_filename
872*9c5db199SXin Li     urls = ('http://commondatastorage.googleapis.com/chromeos-mirror/gentoo/'
873*9c5db199SXin Li             'distfiles/%s' % url_filename,)
874*9c5db199SXin Li     hex_sum = '76c37504251171fda8da8e63ecb8bc42a69a5c81'
875*9c5db199SXin Li
876*9c5db199SXin Li     def _build_and_install(self, install_dir):
877*9c5db199SXin Li         #The path of zlib library might be different from what PIL setup.py is
878*9c5db199SXin Li         #expected. Following change does the best attempt to link the library
879*9c5db199SXin Li         #to a path PIL setup.py will try.
880*9c5db199SXin Li         libz_possible_path = '/usr/lib/x86_64-linux-gnu/libz.so'
881*9c5db199SXin Li         libz_expected_path = '/usr/lib/libz.so'
882*9c5db199SXin Li         # TODO(crbug.com/957186): this sudo command fails if build_externals
883*9c5db199SXin Li         # is running in non-interactive mode, and requires a workaround when
884*9c5db199SXin Li         # running within a docker build process. Remove this operation, or
885*9c5db199SXin Li         # remove this entire package.
886*9c5db199SXin Li         if (os.path.exists(libz_possible_path) and
887*9c5db199SXin Li             not os.path.exists(libz_expected_path)):
888*9c5db199SXin Li             utils.run('sudo ln -s %s %s' %
889*9c5db199SXin Li                       (libz_possible_path, libz_expected_path))
890*9c5db199SXin Li         return self._build_and_install_from_package(install_dir)
891*9c5db199SXin Li
892*9c5db199SXin Li     _build_and_install_current_dir = (
893*9c5db199SXin Li             ExternalPackage._build_and_install_current_dir_noegg)
894*9c5db199SXin Li
895*9c5db199SXin Li
896*9c5db199SXin Liclass AstroidPackage(ExternalPackage):
897*9c5db199SXin Li    """astroid package."""
898*9c5db199SXin Li    version = '1.5.3'
899*9c5db199SXin Li    url_filename = 'astroid-%s.tar.gz' % version
900*9c5db199SXin Li    local_filename = url_filename
901*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
902*9c5db199SXin Li    hex_sum = 'e654225ab5bd2788e5e246b156910990bf33cde6'
903*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
904*9c5db199SXin Li    _build_and_install_current_dir = (
905*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
906*9c5db199SXin Li
907*9c5db199SXin Li
908*9c5db199SXin Liclass LazyObjectProxyPackage(ExternalPackage):
909*9c5db199SXin Li    """lazy-object-proxy package (dependency for astroid)."""
910*9c5db199SXin Li    version = '1.3.1'
911*9c5db199SXin Li    url_filename = 'lazy-object-proxy-%s.tar.gz' % version
912*9c5db199SXin Li    local_filename = url_filename
913*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
914*9c5db199SXin Li    hex_sum = '984828d8f672986ca926373986214d7057b772fb'
915*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
916*9c5db199SXin Li    _build_and_install_current_dir = (
917*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
918*9c5db199SXin Li
919*9c5db199SXin Li
920*9c5db199SXin Liclass SingleDispatchPackage(ExternalPackage):
921*9c5db199SXin Li    """singledispatch package (dependency for astroid)."""
922*9c5db199SXin Li    version = '3.4.0.3'
923*9c5db199SXin Li    url_filename = 'singledispatch-%s.tar.gz' % version
924*9c5db199SXin Li    local_filename = url_filename
925*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
926*9c5db199SXin Li    hex_sum = 'f93241b06754a612af8bb7aa208c4d1805637022'
927*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
928*9c5db199SXin Li    _build_and_install_current_dir = (
929*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
930*9c5db199SXin Li
931*9c5db199SXin Li
932*9c5db199SXin Liclass Enum34Package(ExternalPackage):
933*9c5db199SXin Li    """enum34 package (dependency for astroid)."""
934*9c5db199SXin Li    version = '1.1.6'
935*9c5db199SXin Li    url_filename = 'enum34-%s.tar.gz' % version
936*9c5db199SXin Li    local_filename = url_filename
937*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
938*9c5db199SXin Li    hex_sum = '014ef5878333ff91099893d615192c8cd0b1525a'
939*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
940*9c5db199SXin Li    _build_and_install_current_dir = (
941*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
942*9c5db199SXin Li
943*9c5db199SXin Li
944*9c5db199SXin Liclass WraptPackage(ExternalPackage):
945*9c5db199SXin Li    """wrapt package (dependency for astroid)."""
946*9c5db199SXin Li    version = '1.10.10'
947*9c5db199SXin Li    url_filename = 'wrapt-%s.tar.gz' % version
948*9c5db199SXin Li    local_filename = url_filename
949*9c5db199SXin Li    #md5=97365e906afa8b431f266866ec4e2e18
950*9c5db199SXin Li    urls = ('https://pypi.python.org/packages/a3/bb/'
951*9c5db199SXin Li            '525e9de0a220060394f4aa34fdf6200853581803d92714ae41fc3556e7d7/%s' %
952*9c5db199SXin Li            (url_filename),)
953*9c5db199SXin Li    hex_sum = '6be4f1bb50db879863f4247692360eb830a3eb33'
954*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
955*9c5db199SXin Li    _build_and_install_current_dir = (
956*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_noegg)
957*9c5db199SXin Li
958*9c5db199SXin Li
959*9c5db199SXin Liclass SixPackage(ExternalPackage):
960*9c5db199SXin Li    """six package (dependency for astroid)."""
961*9c5db199SXin Li    version = '1.10.0'
962*9c5db199SXin Li    url_filename = 'six-%s.tar.gz' % version
963*9c5db199SXin Li    local_filename = url_filename
964*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
965*9c5db199SXin Li    hex_sum = '30d480d2e352e8e4c2aae042cf1bf33368ff0920'
966*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
967*9c5db199SXin Li    _build_and_install_current_dir = (
968*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
969*9c5db199SXin Li
970*9c5db199SXin Li
971*9c5db199SXin Liclass SetuptoolsScmPackage(ExternalPackage):
972*9c5db199SXin Li    """setuptools_scm package."""
973*9c5db199SXin Li    version = '5.0.2'
974*9c5db199SXin Li    url_filename = 'setuptools_scm-%s.tar.gz' % version
975*9c5db199SXin Li    local_filename = url_filename
976*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename, )
977*9c5db199SXin Li    hex_sum = '28ec9ce4a5270f82f07e919398c74221da67a8bb'
978*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
979*9c5db199SXin Li    _build_and_install_current_dir = (
980*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
981*9c5db199SXin Li
982*9c5db199SXin Li
983*9c5db199SXin Liclass LruCachePackage(ExternalPackage):
984*9c5db199SXin Li    """backports.functools_lru_cache package (dependency for astroid)."""
985*9c5db199SXin Li    version = '1.4'
986*9c5db199SXin Li    url_filename = 'backports.functools_lru_cache-%s.tar.gz' % version
987*9c5db199SXin Li    local_filename = url_filename
988*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
989*9c5db199SXin Li    hex_sum = '8a546e7887e961c2873c9b053f4e2cd2a96bd71d'
990*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
991*9c5db199SXin Li    _build_and_install_current_dir = (
992*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
993*9c5db199SXin Li
994*9c5db199SXin Li
995*9c5db199SXin Liclass LogilabCommonPackage(ExternalPackage):
996*9c5db199SXin Li    """logilab-common package."""
997*9c5db199SXin Li    version = '1.2.2'
998*9c5db199SXin Li    module_name = 'logilab'
999*9c5db199SXin Li    url_filename = 'logilab-common-%s.tar.gz' % version
1000*9c5db199SXin Li    local_filename = url_filename
1001*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
1002*9c5db199SXin Li    hex_sum = 'ecad2d10c31dcf183c8bed87b6ec35e7ed397d27'
1003*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
1004*9c5db199SXin Li    _build_and_install_current_dir = (
1005*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
1006*9c5db199SXin Li
1007*9c5db199SXin Li
1008*9c5db199SXin Liclass PytestRunnerPackage(ExternalPackage):
1009*9c5db199SXin Li    """pytest-runner package."""
1010*9c5db199SXin Li    version = '5.2'
1011*9c5db199SXin Li    url_filename = 'pytest-runner-%s.tar.gz' % version
1012*9c5db199SXin Li    local_filename = url_filename
1013*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
1014*9c5db199SXin Li    hex_sum = '3427663b575c5d885ea3869a1be09aca36517f74'
1015*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
1016*9c5db199SXin Li    _build_and_install_current_dir = (
1017*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
1018*9c5db199SXin Li
1019*9c5db199SXin Li
1020*9c5db199SXin Liclass PyLintPackage(ExternalPackage):
1021*9c5db199SXin Li    """pylint package."""
1022*9c5db199SXin Li    version = '1.7.2'
1023*9c5db199SXin Li    url_filename = 'pylint-%s.tar.gz' % version
1024*9c5db199SXin Li    local_filename = url_filename
1025*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
1026*9c5db199SXin Li    hex_sum = '42d8b9394e5a485377ae128b01350f25d8b131e0'
1027*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
1028*9c5db199SXin Li    _build_and_install_current_dir = (
1029*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
1030*9c5db199SXin Li
1031*9c5db199SXin Li
1032*9c5db199SXin Liclass ConfigParserPackage(ExternalPackage):
1033*9c5db199SXin Li    """configparser package (dependency for pylint)."""
1034*9c5db199SXin Li    version = '3.5.0'
1035*9c5db199SXin Li    url_filename = 'configparser-%s.tar.gz' % version
1036*9c5db199SXin Li    local_filename = url_filename
1037*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
1038*9c5db199SXin Li    hex_sum = '8ee6b29c6a11977c0e094da1d4f5f71e7e7ac78b'
1039*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
1040*9c5db199SXin Li    _build_and_install_current_dir = (
1041*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
1042*9c5db199SXin Li
1043*9c5db199SXin Li
1044*9c5db199SXin Liclass IsortPackage(ExternalPackage):
1045*9c5db199SXin Li    """isort package (dependency for pylint)."""
1046*9c5db199SXin Li    version = '4.2.15'
1047*9c5db199SXin Li    url_filename = 'isort-%s.tar.gz' % version
1048*9c5db199SXin Li    local_filename = url_filename
1049*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
1050*9c5db199SXin Li    hex_sum = 'acacc36e476b70e13e6fda812c193f4c3c187781'
1051*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
1052*9c5db199SXin Li    _build_and_install_current_dir = (
1053*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
1054*9c5db199SXin Li
1055*9c5db199SXin Li
1056*9c5db199SXin Liclass DateutilPackage(ExternalPackage):
1057*9c5db199SXin Li    """python-dateutil package."""
1058*9c5db199SXin Li    version = '2.6.1'
1059*9c5db199SXin Li    local_filename = 'python-dateutil-%s.tar.gz' % version
1060*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
1061*9c5db199SXin Li    hex_sum = 'db2ace298dee7e47fd720ed03eb790885347bf4e'
1062*9c5db199SXin Li
1063*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
1064*9c5db199SXin Li    _build_and_install_current_dir = (
1065*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
1066*9c5db199SXin Li
1067*9c5db199SXin Li
1068*9c5db199SXin Liclass PyYAMLPackage(ExternalPackage):
1069*9c5db199SXin Li    """pyyaml package."""
1070*9c5db199SXin Li    version = '3.12'
1071*9c5db199SXin Li    local_filename = 'PyYAML-%s.tar.gz' % version
1072*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
1073*9c5db199SXin Li    hex_sum = 'cb7fd3e58c129494ee86e41baedfec69eb7dafbe'
1074*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
1075*9c5db199SXin Li    _build_and_install_current_dir = (
1076*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_noegg)
1077*9c5db199SXin Li
1078*9c5db199SXin Li
1079*9c5db199SXin Liclass GoogleAuthPackage(ExternalPackage):
1080*9c5db199SXin Li    """Google Auth Client."""
1081*9c5db199SXin Li    version = '1.6.3'
1082*9c5db199SXin Li    local_filename = 'google-auth-%s.tar.gz' % version
1083*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
1084*9c5db199SXin Li    hex_sum = 'a76f97686ebe42097d91e0996a72b26b54118f3b'
1085*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
1086*9c5db199SXin Li    _build_and_install_current_dir = (
1087*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
1088*9c5db199SXin Li
1089*9c5db199SXin Li
1090*9c5db199SXin Liclass GrpcioPackage(ExternalPackage):
1091*9c5db199SXin Li    """GrpcioPackage package."""
1092*9c5db199SXin Li    version = '1.26.0'
1093*9c5db199SXin Li    hex_sum = "b9a61f855bf3656d9b8ac305bd1e52442e120c48"
1094*9c5db199SXin Li    local_filename = 'grpcio-%s.tar.gz' % version
1095*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
1096*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
1097*9c5db199SXin Li    _build_and_install_current_dir = (
1098*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
1099*9c5db199SXin Li
1100*9c5db199SXin Li
1101*9c5db199SXin Liclass GrpcioToolsPackage(ExternalPackage):
1102*9c5db199SXin Li    """GrpcioPackage package."""
1103*9c5db199SXin Li    version = '1.26.0'
1104*9c5db199SXin Li    hex_sum = "298724d8704523c6ff443303e0c26fc1d54f9acb"
1105*9c5db199SXin Li    local_filename = 'grpcio-tools-%s.tar.gz' % version
1106*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
1107*9c5db199SXin Li    _build_and_install = ExternalPackage._build_and_install_from_package
1108*9c5db199SXin Li    _build_and_install_current_dir = (
1109*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
1110*9c5db199SXin Li
1111*9c5db199SXin Li
1112*9c5db199SXin Liclass Protobuf(ExternalPackage):
1113*9c5db199SXin Li    """GrpcioPackage package."""
1114*9c5db199SXin Li    version = '3.11.2'
1115*9c5db199SXin Li    hex_sum = "e1f3ffa028ece5a529149dd56a3d64aea4ae1b1a"
1116*9c5db199SXin Li    local_filename = 'protobuf-%s.tar.gz' % version
1117*9c5db199SXin Li    urls = (_CHROMEOS_MIRROR + local_filename,)
1118*9c5db199SXin Li    _build_and_install_current_dir = (
1119*9c5db199SXin Li            ExternalPackage._build_and_install_current_dir_setup_py)
1120*9c5db199SXin Li
1121*9c5db199SXin Li    def _build_and_install(self, install_dir):
1122*9c5db199SXin Li        """
1123*9c5db199SXin Li        This method may be used as a _build_and_install() implementation
1124*9c5db199SXin Li        for subclasses if they implement _build_and_install_current_dir().
1125*9c5db199SXin Li
1126*9c5db199SXin Li        Extracts the .tar.gz file, chdirs into the extracted directory
1127*9c5db199SXin Li        (which is assumed to match the tar filename) and calls
1128*9c5db199SXin Li        _build_and_isntall_current_dir from there.
1129*9c5db199SXin Li
1130*9c5db199SXin Li        Afterwards the build (regardless of failure) extracted .tar.gz
1131*9c5db199SXin Li        directory is cleaned up.
1132*9c5db199SXin Li
1133*9c5db199SXin Li        @returns True on success, False otherwise.
1134*9c5db199SXin Li
1135*9c5db199SXin Li        @raises OSError If the expected extraction directory does not exist.
1136*9c5db199SXin Li        """
1137*9c5db199SXin Li        self._extract_compressed_package()
1138*9c5db199SXin Li        extension = self._get_extension(self.verified_package)
1139*9c5db199SXin Li        os.chdir(os.path.dirname(self.verified_package))
1140*9c5db199SXin Li        os.chdir(os.path.join(self.extracted_package_path, "python"))
1141*9c5db199SXin Li        extracted_dir = os.getcwd()
1142*9c5db199SXin Li        try:
1143*9c5db199SXin Li            return self._build_and_install_current_dir(install_dir)
1144*9c5db199SXin Li        finally:
1145*9c5db199SXin Li            os.chdir(os.path.join(extracted_dir, '..'))
1146*9c5db199SXin Li            shutil.rmtree(extracted_dir)
1147*9c5db199SXin Li
1148*9c5db199SXin Li
1149*9c5db199SXin Liclass _ExternalGitRepo(ExternalPackage):
1150*9c5db199SXin Li    """
1151*9c5db199SXin Li    Parent class for any package which needs to pull a git repo.
1152*9c5db199SXin Li
1153*9c5db199SXin Li    This class inherits from ExternalPackage only so we can sync git
1154*9c5db199SXin Li    repos through the build_externals script. We do not reuse any of
1155*9c5db199SXin Li    ExternalPackage's other methods. Any package that needs a git repo
1156*9c5db199SXin Li    should subclass this and override build_and_install or fetch as
1157*9c5db199SXin Li    they see appropriate.
1158*9c5db199SXin Li    """
1159*9c5db199SXin Li
1160*9c5db199SXin Li    os_requirements = {('/usr/bin/git') : 'git-core'}
1161*9c5db199SXin Li
1162*9c5db199SXin Li    # All the chromiumos projects used on the lab servers should have a 'prod'
1163*9c5db199SXin Li    # branch used to track the software version deployed in prod.
1164*9c5db199SXin Li    PROD_BRANCH = 'prod'
1165*9c5db199SXin Li
1166*9c5db199SXin Li    def is_needed(self, unused_install_dir):
1167*9c5db199SXin Li        """Tell build_externals that we need to fetch."""
1168*9c5db199SXin Li        # TODO(beeps): check if we're already upto date.
1169*9c5db199SXin Li        return True
1170*9c5db199SXin Li
1171*9c5db199SXin Li
1172*9c5db199SXin Li    def build_and_install(self, unused_install_dir):
1173*9c5db199SXin Li        """
1174*9c5db199SXin Li        Fall through method to install a package.
1175*9c5db199SXin Li
1176*9c5db199SXin Li        Overwritten in base classes to pull a git repo.
1177*9c5db199SXin Li        """
1178*9c5db199SXin Li        raise NotImplementedError
1179*9c5db199SXin Li
1180*9c5db199SXin Li
1181*9c5db199SXin Li    def fetch(self, unused_dest_dir):
1182*9c5db199SXin Li        """Fallthrough method to fetch a package."""
1183*9c5db199SXin Li        return True
1184*9c5db199SXin Li
1185*9c5db199SXin Li
1186*9c5db199SXin Liclass HdctoolsRepo(_ExternalGitRepo):
1187*9c5db199SXin Li    """Clones or updates the hdctools repo."""
1188*9c5db199SXin Li
1189*9c5db199SXin Li    module_name = 'servo'
1190*9c5db199SXin Li    temp_hdctools_dir = tempfile.mktemp(suffix='hdctools')
1191*9c5db199SXin Li    _GIT_URL = ('https://chromium.googlesource.com/'
1192*9c5db199SXin Li                'chromiumos/third_party/hdctools')
1193*9c5db199SXin Li    MAIN_BRANCH = 'main'
1194*9c5db199SXin Li
1195*9c5db199SXin Li    def fetch(self, unused_dest_dir):
1196*9c5db199SXin Li        """
1197*9c5db199SXin Li        Fetch repo to a temporary location.
1198*9c5db199SXin Li
1199*9c5db199SXin Li        We use an intermediate temp directory to stage our
1200*9c5db199SXin Li        installation because we only care about the servo package.
1201*9c5db199SXin Li        If we can't get at the top commit hash after fetching
1202*9c5db199SXin Li        something is wrong. This can happen when we've cloned/pulled
1203*9c5db199SXin Li        an empty repo. Not something we expect to do.
1204*9c5db199SXin Li
1205*9c5db199SXin Li        @parma unused_dest_dir: passed in because we inherit from
1206*9c5db199SXin Li            ExternalPackage.
1207*9c5db199SXin Li
1208*9c5db199SXin Li        @return: True if repo sync was successful.
1209*9c5db199SXin Li        """
1210*9c5db199SXin Li        git_repo = revision_control.GitRepo(
1211*9c5db199SXin Li                        self.temp_hdctools_dir,
1212*9c5db199SXin Li                        self._GIT_URL,
1213*9c5db199SXin Li                        None,
1214*9c5db199SXin Li                        abs_work_tree=self.temp_hdctools_dir)
1215*9c5db199SXin Li        git_repo.reinit_repo_at(self.PROD_BRANCH)
1216*9c5db199SXin Li
1217*9c5db199SXin Li        if git_repo.get_latest_commit_hash():
1218*9c5db199SXin Li            return True
1219*9c5db199SXin Li        return False
1220*9c5db199SXin Li
1221*9c5db199SXin Li
1222*9c5db199SXin Li    def build_and_install(self, install_dir):
1223*9c5db199SXin Li        """Reach into the hdctools repo and rsync only the servo directory."""
1224*9c5db199SXin Li
1225*9c5db199SXin Li        servo_dir = os.path.join(self.temp_hdctools_dir, 'servo')
1226*9c5db199SXin Li        if not os.path.exists(servo_dir):
1227*9c5db199SXin Li            return False
1228*9c5db199SXin Li
1229*9c5db199SXin Li        rv = self._rsync(servo_dir, os.path.join(install_dir, 'servo'))
1230*9c5db199SXin Li        shutil.rmtree(self.temp_hdctools_dir)
1231*9c5db199SXin Li        return rv
1232*9c5db199SXin Li
1233*9c5db199SXin Li
1234*9c5db199SXin Liclass ChromiteRepo(_ExternalGitRepo):
1235*9c5db199SXin Li    """Clones or updates the chromite repo."""
1236*9c5db199SXin Li
1237*9c5db199SXin Li    _GIT_URL = ('https://chromium.googlesource.com/chromiumos/chromite')
1238*9c5db199SXin Li    MAIN_BRANCH = 'main'
1239*9c5db199SXin Li
1240*9c5db199SXin Li    def build_and_install(self, install_dir, main_branch=False):
1241*9c5db199SXin Li        """
1242*9c5db199SXin Li        Clone if the repo isn't initialized, pull clean bits if it is.
1243*9c5db199SXin Li
1244*9c5db199SXin Li        Unlike it's hdctools counterpart the chromite repo clones main
1245*9c5db199SXin Li        directly into site-packages. It doesn't use an intermediate temp
1246*9c5db199SXin Li        directory because it doesn't need installation.
1247*9c5db199SXin Li
1248*9c5db199SXin Li        @param install_dir: destination directory for chromite installation.
1249*9c5db199SXin Li        @param main_branch: if True, install main branch. Otherwise,
1250*9c5db199SXin Li                              install prod branch.
1251*9c5db199SXin Li        """
1252*9c5db199SXin Li        init_branch = (self.MAIN_BRANCH if main_branch
1253*9c5db199SXin Li                       else self.PROD_BRANCH)
1254*9c5db199SXin Li        local_chromite_dir = os.path.join(install_dir, 'chromite')
1255*9c5db199SXin Li        git_repo = revision_control.GitRepo(
1256*9c5db199SXin Li                local_chromite_dir,
1257*9c5db199SXin Li                self._GIT_URL,
1258*9c5db199SXin Li                abs_work_tree=local_chromite_dir)
1259*9c5db199SXin Li        git_repo.reinit_repo_at(init_branch)
1260*9c5db199SXin Li
1261*9c5db199SXin Li
1262*9c5db199SXin Li        if git_repo.get_latest_commit_hash():
1263*9c5db199SXin Li            return True
1264*9c5db199SXin Li        return False
1265*9c5db199SXin Li
1266*9c5db199SXin Li
1267*9c5db199SXin Liclass BtsocketRepo(_ExternalGitRepo):
1268*9c5db199SXin Li    """Clones or updates the btsocket repo."""
1269*9c5db199SXin Li
1270*9c5db199SXin Li    _GIT_URL = ('https://chromium.googlesource.com/'
1271*9c5db199SXin Li                'chromiumos/platform/btsocket')
1272*9c5db199SXin Li    # TODO b:169251326 terms below are set outside of this codebase and should
1273*9c5db199SXin Li    # be updated when possible ("master" -> "main").
1274*9c5db199SXin Li    MAIN_BRANCH = 'master'
1275*9c5db199SXin Li
1276*9c5db199SXin Li    def fetch(self, unused_dest_dir):
1277*9c5db199SXin Li        """
1278*9c5db199SXin Li        Fetch repo to a temporary location.
1279*9c5db199SXin Li
1280*9c5db199SXin Li        We use an intermediate temp directory because we have to build an
1281*9c5db199SXin Li        egg for installation.  If we can't get at the top commit hash after
1282*9c5db199SXin Li        fetching something is wrong. This can happen when we've cloned/pulled
1283*9c5db199SXin Li        an empty repo. Not something we expect to do.
1284*9c5db199SXin Li
1285*9c5db199SXin Li        @parma unused_dest_dir: passed in because we inherit from
1286*9c5db199SXin Li            ExternalPackage.
1287*9c5db199SXin Li
1288*9c5db199SXin Li        @return: True if repo sync was successful.
1289*9c5db199SXin Li        """
1290*9c5db199SXin Li        self.temp_btsocket_dir = autotemp.tempdir(unique_id='btsocket')
1291*9c5db199SXin Li        try:
1292*9c5db199SXin Li            git_repo = revision_control.GitRepo(
1293*9c5db199SXin Li                            self.temp_btsocket_dir.name,
1294*9c5db199SXin Li                            self._GIT_URL,
1295*9c5db199SXin Li                            None,
1296*9c5db199SXin Li                            abs_work_tree=self.temp_btsocket_dir.name)
1297*9c5db199SXin Li            git_repo.reinit_repo_at(self.PROD_BRANCH)
1298*9c5db199SXin Li
1299*9c5db199SXin Li            if git_repo.get_latest_commit_hash():
1300*9c5db199SXin Li                return True
1301*9c5db199SXin Li        except:
1302*9c5db199SXin Li            self.temp_btsocket_dir.clean()
1303*9c5db199SXin Li            raise
1304*9c5db199SXin Li
1305*9c5db199SXin Li        self.temp_btsocket_dir.clean()
1306*9c5db199SXin Li        return False
1307*9c5db199SXin Li
1308*9c5db199SXin Li
1309*9c5db199SXin Li    def build_and_install(self, install_dir):
1310*9c5db199SXin Li        """
1311*9c5db199SXin Li        Install the btsocket module using setup.py
1312*9c5db199SXin Li
1313*9c5db199SXin Li        @param install_dir: Target installation directory.
1314*9c5db199SXin Li
1315*9c5db199SXin Li        @return: A boolean indicating success of failure.
1316*9c5db199SXin Li        """
1317*9c5db199SXin Li        work_dir = os.getcwd()
1318*9c5db199SXin Li        try:
1319*9c5db199SXin Li            os.chdir(self.temp_btsocket_dir.name)
1320*9c5db199SXin Li            rv = self._build_and_install_current_dir_setup_py(install_dir)
1321*9c5db199SXin Li        finally:
1322*9c5db199SXin Li            os.chdir(work_dir)
1323*9c5db199SXin Li            self.temp_btsocket_dir.clean()
1324*9c5db199SXin Li        return rv
1325*9c5db199SXin Li
1326*9c5db199SXin Li
1327*9c5db199SXin Liclass SkylabInventoryRepo(_ExternalGitRepo):
1328*9c5db199SXin Li    """Clones or updates the skylab_inventory repo."""
1329*9c5db199SXin Li
1330*9c5db199SXin Li    _GIT_URL = ('https://chromium.googlesource.com/chromiumos/infra/'
1331*9c5db199SXin Li                'skylab_inventory')
1332*9c5db199SXin Li    # TODO b:169251326 terms below are set outside of this codebase and should
1333*9c5db199SXin Li    # be updated when possible ("master" -> "main").
1334*9c5db199SXin Li    MAIN_BRANCH = 'master'
1335*9c5db199SXin Li
1336*9c5db199SXin Li    # TODO(nxia): create a prod branch for skylab_inventory.
1337*9c5db199SXin Li    def build_and_install(self, install_dir):
1338*9c5db199SXin Li        """
1339*9c5db199SXin Li        @param install_dir: destination directory for skylab_inventory
1340*9c5db199SXin Li                            installation.
1341*9c5db199SXin Li        """
1342*9c5db199SXin Li        local_skylab_dir = os.path.join(install_dir, 'infra_skylab_inventory')
1343*9c5db199SXin Li        git_repo = revision_control.GitRepo(
1344*9c5db199SXin Li                local_skylab_dir,
1345*9c5db199SXin Li                self._GIT_URL,
1346*9c5db199SXin Li                abs_work_tree=local_skylab_dir)
1347*9c5db199SXin Li        git_repo.reinit_repo_at(self.MAIN_BRANCH)
1348*9c5db199SXin Li
1349*9c5db199SXin Li        # The top-level __init__.py for skylab is at venv/skylab_inventory.
1350*9c5db199SXin Li        source = os.path.join(local_skylab_dir, 'venv', 'skylab_inventory')
1351*9c5db199SXin Li        link_name = os.path.join(install_dir, 'skylab_inventory')
1352*9c5db199SXin Li
1353*9c5db199SXin Li        if (os.path.exists(link_name) and
1354*9c5db199SXin Li            os.path.realpath(link_name) != os.path.realpath(source)):
1355*9c5db199SXin Li            os.remove(link_name)
1356*9c5db199SXin Li
1357*9c5db199SXin Li        if not os.path.exists(link_name):
1358*9c5db199SXin Li            os.symlink(source, link_name)
1359*9c5db199SXin Li
1360*9c5db199SXin Li        if git_repo.get_latest_commit_hash():
1361*9c5db199SXin Li            return True
1362*9c5db199SXin Li        return False
1363