1 #!/usr/bin/python 2 # 3 # Please keep this code python 2.4 compatible and standalone. 4 5 """ 6 Fetch, build and install external Python library dependancies. 7 8 This fetches external python libraries, builds them using your host's 9 python and installs them under our own autotest/site-packages/ directory. 10 11 Usage? Just run it. 12 utils/build_externals.py 13 """ 14 15 import argparse 16 import compileall 17 import logging 18 import os 19 import sys 20 21 import common 22 from autotest_lib.client.common_lib import logging_config, logging_manager 23 from autotest_lib.client.common_lib import utils 24 from autotest_lib.utils import external_packages 25 26 # bring in site packages as well 27 utils.import_site_module(__file__, 'autotest_lib.utils.site_external_packages') 28 29 # Where package source be fetched to relative to the top of the autotest tree. 30 PACKAGE_DIR = 'ExternalSource' 31 32 # Where packages will be installed to relative to the top of the autotest tree. 33 INSTALL_DIR = 'site-packages' 34 35 # Installs all packages, even if the system already has the version required 36 INSTALL_ALL = False 37 38 39 # Want to add more packages to fetch, build and install? See the class 40 # definitions at the end of external_packages.py for examples of how to do it. 41 42 43 class BuildExternalsLoggingConfig(logging_config.LoggingConfig): 44 """Logging manager config.""" 45 46 def configure_logging(self, results_dir=None, verbose=False): 47 """Configure logging.""" 48 super(BuildExternalsLoggingConfig, self).configure_logging( 49 use_console=True, 50 verbose=verbose) 51 52 53 def main(): 54 """ 55 Find all ExternalPackage classes defined in this file and ask them to 56 fetch, build and install themselves. 57 """ 58 options = parse_arguments(sys.argv[1:]) 59 logging_manager.configure_logging(BuildExternalsLoggingConfig(), 60 verbose=True) 61 os.umask(022) 62 63 top_of_tree = external_packages.find_top_of_autotest_tree() 64 package_dir = os.path.join(top_of_tree, PACKAGE_DIR) 65 install_dir = os.path.join(top_of_tree, INSTALL_DIR) 66 67 # 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. 70 if install_dir not in sys.path: 71 sys.path.insert(0, install_dir) 72 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]) 77 78 fetched_packages, fetch_errors = fetch_necessary_packages( 79 package_dir, install_dir, set(options.names_to_check)) 80 install_errors = build_and_install_packages( 81 fetched_packages, install_dir, options.use_chromite_master) 82 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. 88 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. 92 external_packages.system("chmod -R a+rX '%s'" % install_dir) 93 94 errors = fetch_errors + install_errors 95 for error_msg in errors: 96 logging.error(error_msg) 97 98 if not errors: 99 logging.info("Syntax errors from pylint above are expected, not " 100 "problematic. SUCCESS.") 101 else: 102 logging.info("Problematic errors encountered. FAILURE.") 103 return len(errors) 104 105 106 def 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 125 def fetch_necessary_packages(dest_dir, install_dir, names_to_check=set()): 126 """ 127 Fetches all ExternalPackages into dest_dir. 128 129 @param dest_dir: Directory the packages should be fetched into. 130 @param install_dir: Directory where packages will later installed. 131 @param names_to_check: A set of package names to check whether they are 132 needed on current system. Default is empty. 133 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 = [] 141 for package_class in external_packages.ExternalPackage.subclasses: 142 package = package_class() 143 if names_to_check and package.name.lower() not in names_to_check: 144 continue 145 if not package.is_needed(install_dir): 146 logging.info('A new %s is not needed on this system.', 147 package.name) 148 if INSTALL_ALL: 149 logging.info('Installing anyways...') 150 else: 151 continue 152 if not package.fetch(dest_dir): 153 msg = 'Unable to download %s' % package.name 154 logging.error(msg) 155 errors.append(msg) 156 else: 157 fetched_packages.append(package) 158 159 return fetched_packages, errors 160 161 162 def build_and_install_packages(packages, install_dir, 163 use_chromite_master=False): 164 """ 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. 169 @param use_chromite_master: True if updating chromite to master branch. 170 171 @returns A list of error messages for any installs that failed. 172 """ 173 errors = [] 174 for package in packages: 175 if use_chromite_master and package.name.lower() == 'chromiterepo': 176 result = package.build_and_install(install_dir, master_branch=True) 177 else: 178 result = package.build_and_install(install_dir) 179 if isinstance(result, bool): 180 success = result 181 message = None 182 else: 183 success = result[0] 184 message = result[1] 185 if not success: 186 msg = ('Unable to build and install %s.\nError: %s' % 187 (package.name, message)) 188 logging.error(msg) 189 errors.append(msg) 190 return errors 191 192 193 if __name__ == '__main__': 194 sys.exit(main()) 195