xref: /aosp_15_r20/external/autotest/utils/build_externals.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li#!/usr/bin/python2
2*9c5db199SXin Li#
3*9c5db199SXin Li# Please keep this code python 2.4 compatible and standalone.
4*9c5db199SXin Li
5*9c5db199SXin Li"""
6*9c5db199SXin LiFetch, build and install external Python library dependancies.
7*9c5db199SXin Li
8*9c5db199SXin LiThis fetches external python libraries, builds them using your host's
9*9c5db199SXin Lipython and installs them under our own autotest/site-packages/ directory.
10*9c5db199SXin Li
11*9c5db199SXin LiUsage?  Just run it.
12*9c5db199SXin Li    utils/build_externals.py
13*9c5db199SXin Li"""
14*9c5db199SXin Li
15*9c5db199SXin Liimport argparse
16*9c5db199SXin Liimport compileall
17*9c5db199SXin Liimport logging
18*9c5db199SXin Liimport os
19*9c5db199SXin Liimport sys
20*9c5db199SXin Li
21*9c5db199SXin Liimport common
22*9c5db199SXin Lifrom autotest_lib.client.common_lib import logging_config, logging_manager
23*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils
24*9c5db199SXin Lifrom autotest_lib.utils import external_packages
25*9c5db199SXin Li
26*9c5db199SXin Li# bring in site packages as well
27*9c5db199SXin Liutils.import_site_module(__file__, 'autotest_lib.utils.site_external_packages')
28*9c5db199SXin Li
29*9c5db199SXin Li# Where package source be fetched to relative to the top of the autotest tree.
30*9c5db199SXin LiPACKAGE_DIR = 'ExternalSource'
31*9c5db199SXin Li
32*9c5db199SXin Li# Where packages will be installed to relative to the top of the autotest tree.
33*9c5db199SXin LiINSTALL_DIR = 'site-packages'
34*9c5db199SXin Li
35*9c5db199SXin Li# Installs all packages, even if the system already has the version required
36*9c5db199SXin LiINSTALL_ALL = False
37*9c5db199SXin Li
38*9c5db199SXin Li
39*9c5db199SXin Li# Want to add more packages to fetch, build and install?  See the class
40*9c5db199SXin Li# definitions at the end of external_packages.py for examples of how to do it.
41*9c5db199SXin Li
42*9c5db199SXin Li
43*9c5db199SXin Liclass BuildExternalsLoggingConfig(logging_config.LoggingConfig):
44*9c5db199SXin Li    """Logging manager config."""
45*9c5db199SXin Li
46*9c5db199SXin Li    def configure_logging(self, results_dir=None, verbose=False):
47*9c5db199SXin Li        """Configure logging."""
48*9c5db199SXin Li        super(BuildExternalsLoggingConfig, self).configure_logging(
49*9c5db199SXin Li                                                               use_console=True,
50*9c5db199SXin Li                                                               verbose=verbose)
51*9c5db199SXin Li
52*9c5db199SXin Li
53*9c5db199SXin Lidef main():
54*9c5db199SXin Li    """
55*9c5db199SXin Li    Find all ExternalPackage classes defined in this file and ask them to
56*9c5db199SXin Li    fetch, build and install themselves.
57*9c5db199SXin Li    """
58*9c5db199SXin Li    options = parse_arguments(sys.argv[1:])
59*9c5db199SXin Li    logging_manager.configure_logging(BuildExternalsLoggingConfig(),
60*9c5db199SXin Li                                      verbose=True)
61*9c5db199SXin Li    os.umask(0o22)
62*9c5db199SXin Li
63*9c5db199SXin Li    top_of_tree = external_packages.find_top_of_autotest_tree()
64*9c5db199SXin Li    package_dir = os.path.join(top_of_tree, PACKAGE_DIR)
65*9c5db199SXin Li    install_dir = os.path.join(top_of_tree, INSTALL_DIR)
66*9c5db199SXin Li
67*9c5db199SXin Li    # Make sure the install_dir is in our python module search path
68*9c5db199SXin Li    # as well as the PYTHONPATH being used by all our setup.py
69*9c5db199SXin Li    # install subprocesses.
70*9c5db199SXin Li    if install_dir not in sys.path:
71*9c5db199SXin Li        sys.path.insert(0, install_dir)
72*9c5db199SXin Li    env_python_path_varname = 'PYTHONPATH'
73*9c5db199SXin Li    env_python_path = os.environ.get(env_python_path_varname, '')
74*9c5db199SXin Li    if install_dir+':' not in env_python_path:
75*9c5db199SXin Li        os.environ[env_python_path_varname] = ':'.join([
76*9c5db199SXin Li            install_dir, env_python_path])
77*9c5db199SXin Li
78*9c5db199SXin Li    fetched_packages, fetch_errors = fetch_necessary_packages(
79*9c5db199SXin Li        package_dir, install_dir, set(options.names_to_check))
80*9c5db199SXin Li    install_errors = build_and_install_packages(fetched_packages, install_dir,
81*9c5db199SXin Li                                                options.use_chromite_main)
82*9c5db199SXin Li
83*9c5db199SXin Li    # Byte compile the code after it has been installed in its final
84*9c5db199SXin Li    # location as .pyc files contain the path passed to compile_dir().
85*9c5db199SXin Li    # When printing exception tracebacks, python uses that path first to look
86*9c5db199SXin Li    # for the source code before checking the directory of the .pyc file.
87*9c5db199SXin Li    # Don't leave references to our temporary build dir in the files.
88*9c5db199SXin Li    logging.info('compiling .py files in %s to .pyc', install_dir)
89*9c5db199SXin Li    compileall.compile_dir(install_dir, quiet=True)
90*9c5db199SXin Li
91*9c5db199SXin Li    # Some things install with whacky permissions, fix that.
92*9c5db199SXin Li    external_packages.system("chmod -R a+rX '%s'" % install_dir)
93*9c5db199SXin Li
94*9c5db199SXin Li    errors = fetch_errors + install_errors
95*9c5db199SXin Li    for error_msg in errors:
96*9c5db199SXin Li        logging.error(error_msg)
97*9c5db199SXin Li
98*9c5db199SXin Li    if not errors:
99*9c5db199SXin Li      logging.info("Syntax errors from pylint above are expected, not "
100*9c5db199SXin Li                   "problematic. SUCCESS.")
101*9c5db199SXin Li    else:
102*9c5db199SXin Li      logging.info("Problematic errors encountered. FAILURE.")
103*9c5db199SXin Li    return len(errors)
104*9c5db199SXin Li
105*9c5db199SXin Li
106*9c5db199SXin Lidef parse_arguments(args):
107*9c5db199SXin Li    """Parse command line arguments.
108*9c5db199SXin Li
109*9c5db199SXin Li    @param args: The command line arguments to parse. (ususally sys.argsv[1:])
110*9c5db199SXin Li
111*9c5db199SXin Li    @returns An argparse.Namespace populated with argument values.
112*9c5db199SXin Li    """
113*9c5db199SXin Li    parser = argparse.ArgumentParser(
114*9c5db199SXin Li            description='Command to build third party dependencies required '
115*9c5db199SXin Li                        'for autotest.')
116*9c5db199SXin Li    parser.add_argument('--use_chromite_main', action='store_true',
117*9c5db199SXin Li                        help='Update chromite to main branch, rather than '
118*9c5db199SXin Li                             'prod.')
119*9c5db199SXin Li    parser.add_argument('--names_to_check', nargs='*', type=str, default=set(),
120*9c5db199SXin Li                        help='Package names to check whether they are needed '
121*9c5db199SXin Li                             'in current system.')
122*9c5db199SXin Li    return parser.parse_args(args)
123*9c5db199SXin Li
124*9c5db199SXin Li
125*9c5db199SXin Lidef fetch_necessary_packages(dest_dir, install_dir, names_to_check=set()):
126*9c5db199SXin Li    """
127*9c5db199SXin Li    Fetches all ExternalPackages into dest_dir.
128*9c5db199SXin Li
129*9c5db199SXin Li    @param dest_dir: Directory the packages should be fetched into.
130*9c5db199SXin Li    @param install_dir: Directory where packages will later installed.
131*9c5db199SXin Li    @param names_to_check: A set of package names to check whether they are
132*9c5db199SXin Li                           needed on current system. Default is empty.
133*9c5db199SXin Li
134*9c5db199SXin Li    @returns A tuple containing two lists:
135*9c5db199SXin Li             * A list of ExternalPackage instances that were fetched and
136*9c5db199SXin Li               need to be installed.
137*9c5db199SXin Li             * A list of error messages for any failed fetches.
138*9c5db199SXin Li    """
139*9c5db199SXin Li    errors = []
140*9c5db199SXin Li    fetched_packages = []
141*9c5db199SXin Li    for package_class in external_packages.ExternalPackage.subclasses:
142*9c5db199SXin Li        package = package_class()
143*9c5db199SXin Li        if names_to_check and package.name.lower() not in names_to_check:
144*9c5db199SXin Li            continue
145*9c5db199SXin Li        if not package.is_needed(install_dir):
146*9c5db199SXin Li            logging.info('A new %s is not needed on this system.',
147*9c5db199SXin Li                         package.name)
148*9c5db199SXin Li            if INSTALL_ALL:
149*9c5db199SXin Li                logging.info('Installing anyways...')
150*9c5db199SXin Li            else:
151*9c5db199SXin Li                continue
152*9c5db199SXin Li        if not package.fetch(dest_dir):
153*9c5db199SXin Li            msg = 'Unable to download %s' % package.name
154*9c5db199SXin Li            logging.error(msg)
155*9c5db199SXin Li            errors.append(msg)
156*9c5db199SXin Li        else:
157*9c5db199SXin Li            fetched_packages.append(package)
158*9c5db199SXin Li
159*9c5db199SXin Li    return fetched_packages, errors
160*9c5db199SXin Li
161*9c5db199SXin Li
162*9c5db199SXin Lidef build_and_install_packages(packages, install_dir,
163*9c5db199SXin Li                               use_chromite_main=True):
164*9c5db199SXin Li    """
165*9c5db199SXin Li    Builds and installs all packages into install_dir.
166*9c5db199SXin Li
167*9c5db199SXin Li    @param packages - A list of already fetched ExternalPackage instances.
168*9c5db199SXin Li    @param install_dir - Directory the packages should be installed into.
169*9c5db199SXin Li    @param use_chromite_main: True if updating chromite to main branch. Due
170*9c5db199SXin Li                                to the non-usage of origin/prod tag, the default
171*9c5db199SXin Li                                value for this argument has been set to True.
172*9c5db199SXin Li                                This argument has not been removed for backward
173*9c5db199SXin Li                                compatibility.
174*9c5db199SXin Li
175*9c5db199SXin Li    @returns A list of error messages for any installs that failed.
176*9c5db199SXin Li    """
177*9c5db199SXin Li    errors = []
178*9c5db199SXin Li    for package in packages:
179*9c5db199SXin Li        if package.name.lower() == 'chromiterepo':
180*9c5db199SXin Li            if not use_chromite_main:
181*9c5db199SXin Li                logging.warning(
182*9c5db199SXin Li                    'Even though use_chromite_main has been set to %s, it '
183*9c5db199SXin Li                    'will be checked out to main as the origin/prod tag '
184*9c5db199SXin Li                    'carries little value now.', use_chromite_main)
185*9c5db199SXin Li            logging.info('Checking out autotest-chromite to main branch.')
186*9c5db199SXin Li            result = package.build_and_install(
187*9c5db199SXin Li                install_dir, main_branch=True)
188*9c5db199SXin Li        else:
189*9c5db199SXin Li            result = package.build_and_install(install_dir)
190*9c5db199SXin Li        if isinstance(result, bool):
191*9c5db199SXin Li            success = result
192*9c5db199SXin Li            message = None
193*9c5db199SXin Li        else:
194*9c5db199SXin Li            success = result[0]
195*9c5db199SXin Li            message = result[1]
196*9c5db199SXin Li        if not success:
197*9c5db199SXin Li            msg = ('Unable to build and install %s.\nError: %s' %
198*9c5db199SXin Li                   (package.name, message))
199*9c5db199SXin Li            logging.error(msg)
200*9c5db199SXin Li            errors.append(msg)
201*9c5db199SXin Li    return errors
202*9c5db199SXin Li
203*9c5db199SXin Li
204*9c5db199SXin Liif __name__ == '__main__':
205*9c5db199SXin Li    sys.exit(main())
206