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