blob: c373139eff9d2a25980afe61e059f111062eb861 [file] [log] [blame]
Mike Frysingerd03e6b52019-08-03 12:49:01 -04001#!/usr/bin/python2
mblighfa29d662009-02-05 00:44:26 +00002#
Craig Bergstrom0bee5982018-06-13 13:56:52 -06003# Please keep this code python 2.4 compatible and standalone.
mblighfa29d662009-02-05 00:44:26 +00004
5"""
6Fetch, build and install external Python library dependancies.
7
8This fetches external python libraries, builds them using your host's
9python and installs them under our own autotest/site-packages/ directory.
10
11Usage? Just run it.
12 utils/build_externals.py
13"""
14
Shuqian Zhao697f7ed2016-10-21 14:58:28 -070015import argparse
16import compileall
17import logging
18import os
19import sys
20
mblighe5699e22009-09-18 16:44:12 +000021import common
22from autotest_lib.client.common_lib import logging_config, logging_manager
lmr6f80e7a2010-02-04 03:18:28 +000023from autotest_lib.client.common_lib import utils
jamesrenff6e5aa2010-02-12 00:46:40 +000024from autotest_lib.utils import external_packages
25
26# bring in site packages as well
27utils.import_site_module(__file__, 'autotest_lib.utils.site_external_packages')
mblighfa29d662009-02-05 00:44:26 +000028
29# Where package source be fetched to relative to the top of the autotest tree.
30PACKAGE_DIR = 'ExternalSource'
31
32# Where packages will be installed to relative to the top of the autotest tree.
33INSTALL_DIR = 'site-packages'
34
mbligh489e0e02009-06-08 16:46:11 +000035# Installs all packages, even if the system already has the version required
36INSTALL_ALL = False
37
mblighfa29d662009-02-05 00:44:26 +000038
39# Want to add more packages to fetch, build and install? See the class
jamesrenff6e5aa2010-02-12 00:46:40 +000040# definitions at the end of external_packages.py for examples of how to do it.
mblighfa29d662009-02-05 00:44:26 +000041
42
mblighe5699e22009-09-18 16:44:12 +000043class BuildExternalsLoggingConfig(logging_config.LoggingConfig):
Craig Bergstrom0bee5982018-06-13 13:56:52 -060044 """Logging manager config."""
45
mblighe5699e22009-09-18 16:44:12 +000046 def configure_logging(self, results_dir=None, verbose=False):
Craig Bergstrom0bee5982018-06-13 13:56:52 -060047 """Configure logging."""
mblighe5699e22009-09-18 16:44:12 +000048 super(BuildExternalsLoggingConfig, self).configure_logging(
49 use_console=True,
50 verbose=verbose)
51
mblighfa29d662009-02-05 00:44:26 +000052
53def main():
54 """
55 Find all ExternalPackage classes defined in this file and ask them to
56 fetch, build and install themselves.
57 """
Shuqian Zhao697f7ed2016-10-21 14:58:28 -070058 options = parse_arguments(sys.argv[1:])
mblighe5699e22009-09-18 16:44:12 +000059 logging_manager.configure_logging(BuildExternalsLoggingConfig(),
60 verbose=True)
mblighb155d0c2009-07-02 19:04:55 +000061 os.umask(022)
mblighfa29d662009-02-05 00:44:26 +000062
jamesrenff6e5aa2010-02-12 00:46:40 +000063 top_of_tree = external_packages.find_top_of_autotest_tree()
mblighfa29d662009-02-05 00:44:26 +000064 package_dir = os.path.join(top_of_tree, PACKAGE_DIR)
65 install_dir = os.path.join(top_of_tree, INSTALL_DIR)
66
lmr7ebba662009-10-23 12:55:09 +000067 # Make sure the install_dir is in our python module search path
68 # as well as the PYTHONPATH being used by all our setup.py
69 # install subprocesses.
mbligh623c5182009-02-07 01:31:31 +000070 if install_dir not in sys.path:
mbligh623c5182009-02-07 01:31:31 +000071 sys.path.insert(0, install_dir)
lmr7ebba662009-10-23 12:55:09 +000072 env_python_path_varname = 'PYTHONPATH'
73 env_python_path = os.environ.get(env_python_path_varname, '')
74 if install_dir+':' not in env_python_path:
75 os.environ[env_python_path_varname] = ':'.join([
76 install_dir, env_python_path])
mbligh623c5182009-02-07 01:31:31 +000077
Shuqian Zhao697f7ed2016-10-21 14:58:28 -070078 fetched_packages, fetch_errors = fetch_necessary_packages(
79 package_dir, install_dir, set(options.names_to_check))
Sanika Kulkarni0135f9f2020-01-02 09:51:54 -080080 install_errors = build_and_install_packages(fetched_packages, install_dir,
81 options.use_chromite_master)
mblighfa29d662009-02-05 00:44:26 +000082
83 # Byte compile the code after it has been installed in its final
84 # location as .pyc files contain the path passed to compile_dir().
85 # When printing exception tracebacks, python uses that path first to look
86 # for the source code before checking the directory of the .pyc file.
87 # Don't leave references to our temporary build dir in the files.
mbligh623c5182009-02-07 01:31:31 +000088 logging.info('compiling .py files in %s to .pyc', install_dir)
89 compileall.compile_dir(install_dir, quiet=True)
90
91 # Some things install with whacky permissions, fix that.
jamesrenff6e5aa2010-02-12 00:46:40 +000092 external_packages.system("chmod -R a+rX '%s'" % install_dir)
mblighfa29d662009-02-05 00:44:26 +000093
94 errors = fetch_errors + install_errors
95 for error_msg in errors:
lmr65bec4f2010-04-08 02:30:35 +000096 logging.error(error_msg)
mblighfa29d662009-02-05 00:44:26 +000097
Craig Bergstrom0bee5982018-06-13 13:56:52 -060098 if not errors:
Jacob Kopczynski2dc409a2018-06-13 11:24:46 -070099 logging.info("Syntax errors from pylint above are expected, not "
100 "problematic. SUCCESS.")
Craig Bergstrom0bee5982018-06-13 13:56:52 -0600101 else:
Jacob Kopczynski2dc409a2018-06-13 11:24:46 -0700102 logging.info("Problematic errors encountered. FAILURE.")
mblighfa29d662009-02-05 00:44:26 +0000103 return len(errors)
104
105
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700106def parse_arguments(args):
107 """Parse command line arguments.
108
109 @param args: The command line arguments to parse. (ususally sys.argsv[1:])
110
111 @returns An argparse.Namespace populated with argument values.
112 """
113 parser = argparse.ArgumentParser(
114 description='Command to build third party dependencies required '
115 'for autotest.')
116 parser.add_argument('--use_chromite_master', action='store_true',
117 help='Update chromite to master branch, rather than '
118 'prod.')
119 parser.add_argument('--names_to_check', nargs='*', type=str, default=set(),
120 help='Package names to check whether they are needed '
121 'in current system.')
122 return parser.parse_args(args)
123
124
125def fetch_necessary_packages(dest_dir, install_dir, names_to_check=set()):
mblighfa29d662009-02-05 00:44:26 +0000126 """
127 Fetches all ExternalPackages into dest_dir.
128
mblighb155d0c2009-07-02 19:04:55 +0000129 @param dest_dir: Directory the packages should be fetched into.
130 @param install_dir: Directory where packages will later installed.
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700131 @param names_to_check: A set of package names to check whether they are
132 needed on current system. Default is empty.
mblighfa29d662009-02-05 00:44:26 +0000133
134 @returns A tuple containing two lists:
135 * A list of ExternalPackage instances that were fetched and
136 need to be installed.
137 * A list of error messages for any failed fetches.
138 """
139 errors = []
140 fetched_packages = []
jamesrenff6e5aa2010-02-12 00:46:40 +0000141 for package_class in external_packages.ExternalPackage.subclasses:
mblighfa29d662009-02-05 00:44:26 +0000142 package = package_class()
mbligh623c5182009-02-07 01:31:31 +0000143 if names_to_check and package.name.lower() not in names_to_check:
144 continue
mblighb155d0c2009-07-02 19:04:55 +0000145 if not package.is_needed(install_dir):
mblighfa29d662009-02-05 00:44:26 +0000146 logging.info('A new %s is not needed on this system.',
147 package.name)
mbligh489e0e02009-06-08 16:46:11 +0000148 if INSTALL_ALL:
149 logging.info('Installing anyways...')
150 else:
151 continue
mblighfa29d662009-02-05 00:44:26 +0000152 if not package.fetch(dest_dir):
lmr65bec4f2010-04-08 02:30:35 +0000153 msg = 'Unable to download %s' % package.name
154 logging.error(msg)
mblighfa29d662009-02-05 00:44:26 +0000155 errors.append(msg)
156 else:
157 fetched_packages.append(package)
158
159 return fetched_packages, errors
160
161
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700162def build_and_install_packages(packages, install_dir,
Sanika Kulkarni0135f9f2020-01-02 09:51:54 -0800163 use_chromite_master=True):
mblighfa29d662009-02-05 00:44:26 +0000164 """
165 Builds and installs all packages into install_dir.
166
167 @param packages - A list of already fetched ExternalPackage instances.
168 @param install_dir - Directory the packages should be installed into.
Sanika Kulkarni0135f9f2020-01-02 09:51:54 -0800169 @param use_chromite_master: True if updating chromite to master branch. Due
170 to the non-usage of origin/prod tag, the default
171 value for this argument has been set to True.
172 This argument has not been removed for backward
173 compatibility.
mblighfa29d662009-02-05 00:44:26 +0000174
175 @returns A list of error messages for any installs that failed.
176 """
177 errors = []
178 for package in packages:
Sanika Kulkarni0135f9f2020-01-02 09:51:54 -0800179 if package.name.lower() == 'chromiterepo':
180 if not use_chromite_master:
181 logging.warning(
182 'Even though use_chromite_master has been set to %s, it '
183 'will be checked out to master as the origin/prod tag '
184 'carries little value now.', use_chromite_master)
185 logging.info('Checking out autotest-chromite to master branch.')
186 result = package.build_and_install(
187 install_dir, master_branch=True)
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700188 else:
189 result = package.build_and_install(install_dir)
Dan Shi16c0a502015-07-14 17:29:48 -0700190 if isinstance(result, bool):
191 success = result
192 message = None
193 else:
194 success = result[0]
195 message = result[1]
196 if not success:
197 msg = ('Unable to build and install %s.\nError: %s' %
198 (package.name, message))
lmr65bec4f2010-04-08 02:30:35 +0000199 logging.error(msg)
mblighfa29d662009-02-05 00:44:26 +0000200 errors.append(msg)
201 return errors
202
203
mblighfa29d662009-02-05 00:44:26 +0000204if __name__ == '__main__':
205 sys.exit(main())