1*9c5db199SXin Liimport os 2*9c5db199SXin Liimport re 3*9c5db199SXin Liimport six 4*9c5db199SXin Liimport sys 5*9c5db199SXin Li 6*9c5db199SXin Li# This must run on Python versions less than 2.4. 7*9c5db199SXin Lidirname = os.path.dirname(sys.modules[__name__].__file__) 8*9c5db199SXin Licommon_dir = os.path.abspath(os.path.join(dirname, 'common_lib')) 9*9c5db199SXin Lisys.path.insert(0, common_dir) 10*9c5db199SXin Liimport check_version 11*9c5db199SXin Lisys.path.pop(0) 12*9c5db199SXin Li 13*9c5db199SXin Li 14*9c5db199SXin Lidef _get_pyversion_from_args(): 15*9c5db199SXin Li """Extract, format, & pop the current py_version from args, if provided.""" 16*9c5db199SXin Li py_version = 3 17*9c5db199SXin Li py_version_re = re.compile(r'--py_version=(\w+)\b') 18*9c5db199SXin Li 19*9c5db199SXin Li version_found = False 20*9c5db199SXin Li for i, arg in enumerate(sys.argv): 21*9c5db199SXin Li if not arg.startswith('--py_version'): 22*9c5db199SXin Li continue 23*9c5db199SXin Li result = py_version_re.search(arg) 24*9c5db199SXin Li if result: 25*9c5db199SXin Li if version_found: 26*9c5db199SXin Li raise ValueError('--py_version may only be specified once.') 27*9c5db199SXin Li py_version = result.group(1) 28*9c5db199SXin Li version_found = True 29*9c5db199SXin Li if py_version not in ('2', '3'): 30*9c5db199SXin Li raise ValueError('Python version must be "2" or "3".') 31*9c5db199SXin Li 32*9c5db199SXin Li # Remove the arg so other argparsers don't get grumpy. 33*9c5db199SXin Li sys.argv.pop(i) 34*9c5db199SXin Li 35*9c5db199SXin Li return py_version 36*9c5db199SXin Li 37*9c5db199SXin Li 38*9c5db199SXin Lidef _desired_version(): 39*9c5db199SXin Li """ 40*9c5db199SXin Li Returns desired python version. 41*9c5db199SXin Li 42*9c5db199SXin Li If the PY_VERSION env var is set, just return that. This is the case 43*9c5db199SXin Li when autoserv kicks of autotest on the server side via a job.run(), or 44*9c5db199SXin Li a process created a subprocess. 45*9c5db199SXin Li 46*9c5db199SXin Li Otherwise, parse & pop the sys.argv for the '--py_version' flag. If no 47*9c5db199SXin Li flag is set, default to python 3. 48*9c5db199SXin Li 49*9c5db199SXin Li """ 50*9c5db199SXin Li # Even if the arg is in the env vars, we will attempt to get it from the 51*9c5db199SXin Li # args, so that it can be popped prior to other argparsers hitting. 52*9c5db199SXin Li py_version = _get_pyversion_from_args() 53*9c5db199SXin Li 54*9c5db199SXin Li if os.getenv('PY_VERSION'): 55*9c5db199SXin Li return int(os.getenv('PY_VERSION')) 56*9c5db199SXin Li 57*9c5db199SXin Li os.environ['PY_VERSION'] = str(py_version) 58*9c5db199SXin Li return int(py_version) 59*9c5db199SXin Li 60*9c5db199SXin Li 61*9c5db199SXin Lidesired_version = _desired_version() 62*9c5db199SXin Liif desired_version == sys.version_info.major: 63*9c5db199SXin Li os.environ['AUTOTEST_NO_RESTART'] = 'True' 64*9c5db199SXin Lielse: 65*9c5db199SXin Li # There are cases were this can be set (ie by test_that), but a subprocess 66*9c5db199SXin Li # is launched in the incorrect version. 67*9c5db199SXin Li if os.getenv('AUTOTEST_NO_RESTART'): 68*9c5db199SXin Li del os.environ['AUTOTEST_NO_RESTART'] 69*9c5db199SXin Li check_version.check_python_version(desired_version) 70*9c5db199SXin Li 71*9c5db199SXin Liimport glob, traceback 72*9c5db199SXin Li 73*9c5db199SXin Li 74*9c5db199SXin Lidef import_module(module, from_where): 75*9c5db199SXin Li """Equivalent to 'from from_where import module' 76*9c5db199SXin Li Returns the corresponding module""" 77*9c5db199SXin Li from_module = __import__(from_where, globals(), locals(), [module]) 78*9c5db199SXin Li return getattr(from_module, module) 79*9c5db199SXin Li 80*9c5db199SXin Li 81*9c5db199SXin Lidef _autotest_logging_handle_error(self, record): 82*9c5db199SXin Li """Method to monkey patch into logging.Handler to replace handleError.""" 83*9c5db199SXin Li # The same as the default logging.Handler.handleError but also prints 84*9c5db199SXin Li # out the original record causing the error so there is -some- idea 85*9c5db199SXin Li # about which call caused the logging error. 86*9c5db199SXin Li import logging 87*9c5db199SXin Li if logging.raiseExceptions: 88*9c5db199SXin Li # Avoid recursion as the below output can end up back in here when 89*9c5db199SXin Li # something has *seriously* gone wrong in autotest. 90*9c5db199SXin Li logging.raiseExceptions = 0 91*9c5db199SXin Li sys.stderr.write('Exception occurred formatting message: ' 92*9c5db199SXin Li '%r using args %r\n' % (record.msg, record.args)) 93*9c5db199SXin Li traceback.print_stack() 94*9c5db199SXin Li sys.stderr.write('-' * 50 + '\n') 95*9c5db199SXin Li traceback.print_exc() 96*9c5db199SXin Li sys.stderr.write('Future logging formatting exceptions disabled.\n') 97*9c5db199SXin Li 98*9c5db199SXin Li 99*9c5db199SXin Lidef _monkeypatch_logging_handle_error(): 100*9c5db199SXin Li # Hack out logging.py* 101*9c5db199SXin Li logging_py = os.path.join(os.path.dirname(__file__), 'common_lib', 102*9c5db199SXin Li 'logging.py*') 103*9c5db199SXin Li if glob.glob(logging_py): 104*9c5db199SXin Li os.system('rm -f %s' % logging_py) 105*9c5db199SXin Li 106*9c5db199SXin Li # Monkey patch our own handleError into the logging module's StreamHandler. 107*9c5db199SXin Li # A nicer way of doing this -might- be to have our own logging module define 108*9c5db199SXin Li # an autotest Logger instance that added our own Handler subclass with this 109*9c5db199SXin Li # handleError method in it. But that would mean modifying tons of code. 110*9c5db199SXin Li import logging 111*9c5db199SXin Li assert callable(logging.Handler.handleError) 112*9c5db199SXin Li logging.Handler.handleError = _autotest_logging_handle_error 113*9c5db199SXin Li 114*9c5db199SXin Li 115*9c5db199SXin Lidef _insert_site_packages(root): 116*9c5db199SXin Li # Allow locally installed third party packages to be found 117*9c5db199SXin Li # before any that are installed on the system itself when not. 118*9c5db199SXin Li # running as a client. 119*9c5db199SXin Li # This is primarily for the benefit of frontend and tko so that they 120*9c5db199SXin Li # may use libraries other than those available as system packages. 121*9c5db199SXin Li if six.PY2: 122*9c5db199SXin Li sys.path.insert(0, os.path.join(root, 'site-packages')) 123*9c5db199SXin Li 124*9c5db199SXin Li 125*9c5db199SXin Liimport importlib 126*9c5db199SXin Li 127*9c5db199SXin LiROOT_MODULE_NAME_ALLOW_LIST = ( 128*9c5db199SXin Li 'autotest_lib', 129*9c5db199SXin Li 'autotest_lib.client', 130*9c5db199SXin Li) 131*9c5db199SXin Li 132*9c5db199SXin Li 133*9c5db199SXin Lidef _setup_top_level_symlink(base_path): 134*9c5db199SXin Li """Create a self pointing symlink in the base_path).""" 135*9c5db199SXin Li if os.path.islink(os.path.join(base_path, 'autotest_lib')): 136*9c5db199SXin Li return 137*9c5db199SXin Li os.chdir(base_path) 138*9c5db199SXin Li os.symlink('.', 'autotest_lib') 139*9c5db199SXin Li 140*9c5db199SXin Li 141*9c5db199SXin Lidef _setup_client_symlink(base_path): 142*9c5db199SXin Li """Setup the client symlink for the DUT. 143*9c5db199SXin Li 144*9c5db199SXin Li Creates a "autotest_lib" folder in client, then creates a symlink called 145*9c5db199SXin Li "client" pointing back to ../, as well as an __init__ for the folder. 146*9c5db199SXin Li """ 147*9c5db199SXin Li 148*9c5db199SXin Li def _create_client_symlink(): 149*9c5db199SXin Li os.chdir(autotest_lib_dir) 150*9c5db199SXin Li with open('__init__.py', 'w'): 151*9c5db199SXin Li pass 152*9c5db199SXin Li os.symlink('../', 'client') 153*9c5db199SXin Li 154*9c5db199SXin Li autotest_lib_dir = os.path.join(base_path, 'autotest_lib') 155*9c5db199SXin Li link_path = os.path.join(autotest_lib_dir, 'client') 156*9c5db199SXin Li 157*9c5db199SXin Li # TODO: Use os.makedirs(..., exist_ok=True) after switching to Python 3 158*9c5db199SXin Li if not os.path.isdir(autotest_lib_dir): 159*9c5db199SXin Li try: 160*9c5db199SXin Li os.mkdir(autotest_lib_dir) 161*9c5db199SXin Li except FileExistsError as e: 162*9c5db199SXin Li if not os.path.isdir(autotest_lib_dir): 163*9c5db199SXin Li raise e 164*9c5db199SXin Li 165*9c5db199SXin Li if os.path.islink(link_path): 166*9c5db199SXin Li return 167*9c5db199SXin Li 168*9c5db199SXin Li try: 169*9c5db199SXin Li _create_client_symlink() 170*9c5db199SXin Li # It's possible 2 autotest processes are running at once, and one 171*9c5db199SXin Li # creates the symlink in the time between checking and creating. 172*9c5db199SXin Li # Thus if the symlink DNE, and we cannot create it, check for its 173*9c5db199SXin Li # existence and exit if it exists. 174*9c5db199SXin Li except FileExistsError as e: 175*9c5db199SXin Li if os.path.islink(link_path): 176*9c5db199SXin Li return 177*9c5db199SXin Li raise e 178*9c5db199SXin Li 179*9c5db199SXin Li 180*9c5db199SXin Lidef _symlink_check(base_path, root_dir): 181*9c5db199SXin Li """Verify the required symlinks are present, and add them if not.""" 182*9c5db199SXin Li # Note the starting cwd to later change back to it. 183*9c5db199SXin Li starting_dir = os.getcwd() 184*9c5db199SXin Li if root_dir == 'autotest_lib': 185*9c5db199SXin Li _setup_top_level_symlink(base_path) 186*9c5db199SXin Li elif root_dir == 'autotest_lib.client': 187*9c5db199SXin Li _setup_client_symlink(base_path) 188*9c5db199SXin Li 189*9c5db199SXin Li os.chdir(starting_dir) 190*9c5db199SXin Li 191*9c5db199SXin Li 192*9c5db199SXin Lidef setup(base_path, root_module_name): 193*9c5db199SXin Li _symlink_check(base_path, root_module_name) 194*9c5db199SXin Li if root_module_name not in ROOT_MODULE_NAME_ALLOW_LIST: 195*9c5db199SXin Li raise Exception('Unexpected root module: ' + root_module_name) 196*9c5db199SXin Li 197*9c5db199SXin Li _insert_site_packages(base_path) 198*9c5db199SXin Li 199*9c5db199SXin Li # Ie, server (or just not /client) 200*9c5db199SXin Li if root_module_name == 'autotest_lib': 201*9c5db199SXin Li # Base path is just x/x/x/x/autotest/files 202*9c5db199SXin Li _setup_autotest_lib(base_path) 203*9c5db199SXin Li _preimport_top_level_packages(os.path.join(base_path, 'autotest_lib'), 204*9c5db199SXin Li parent='autotest_lib') 205*9c5db199SXin Li else: # aka, in /client/ 206*9c5db199SXin Li if os.path.exists(os.path.join(os.path.dirname(base_path), 'server')): 207*9c5db199SXin Li 208*9c5db199SXin Li # Takes you from /client/ to /files 209*9c5db199SXin Li # this is because on DUT there is no files/client 210*9c5db199SXin Li autotest_base_path = os.path.dirname(base_path) 211*9c5db199SXin Li 212*9c5db199SXin Li else: 213*9c5db199SXin Li autotest_base_path = base_path 214*9c5db199SXin Li 215*9c5db199SXin Li _setup_autotest_lib(autotest_base_path) 216*9c5db199SXin Li _preimport_top_level_packages(os.path.join(autotest_base_path, 217*9c5db199SXin Li 'autotest_lib'), 218*9c5db199SXin Li parent='autotest_lib') 219*9c5db199SXin Li _preimport_top_level_packages( 220*9c5db199SXin Li os.path.join(autotest_base_path, 'autotest_lib', 'client'), 221*9c5db199SXin Li parent='autotest_lib.client', 222*9c5db199SXin Li ) 223*9c5db199SXin Li 224*9c5db199SXin Li _monkeypatch_logging_handle_error() 225*9c5db199SXin Li 226*9c5db199SXin Li 227*9c5db199SXin Lidef _setup_autotest_lib(path): 228*9c5db199SXin Li sys.path.insert(0, path) 229*9c5db199SXin Li # This is a symlink back to the root directory, that does all the magic. 230*9c5db199SXin Li importlib.import_module('autotest_lib') 231*9c5db199SXin Li sys.path.pop(0) 232*9c5db199SXin Li 233*9c5db199SXin Li 234*9c5db199SXin Lidef _preimport_top_level_packages(root, parent): 235*9c5db199SXin Li # The old code to setup the packages used to fetch the top-level packages 236*9c5db199SXin Li # inside autotest_lib. We keep that behaviour in order to avoid having to 237*9c5db199SXin Li # add import statements for the top-level packages all over the codebase. 238*9c5db199SXin Li # 239*9c5db199SXin Li # e.g., 240*9c5db199SXin Li # import common 241*9c5db199SXin Li # from autotest_lib.server import utils 242*9c5db199SXin Li # 243*9c5db199SXin Li # must continue to work. The _right_ way to do that import would be. 244*9c5db199SXin Li # 245*9c5db199SXin Li # import common 246*9c5db199SXin Li # import autotest_lib.server 247*9c5db199SXin Li # from autotest_lib.server import utils 248*9c5db199SXin Li names = [] 249*9c5db199SXin Li for filename in os.listdir(root): 250*9c5db199SXin Li path = os.path.join(root, filename) 251*9c5db199SXin Li if not os.path.isdir(path): 252*9c5db199SXin Li continue # skip files 253*9c5db199SXin Li if '.' in filename: 254*9c5db199SXin Li continue # if "." is in the name it's not a valid package name 255*9c5db199SXin Li if not os.access(path, os.R_OK | os.X_OK): 256*9c5db199SXin Li continue # need read + exec access to make a dir importable 257*9c5db199SXin Li if '__init__.py' in os.listdir(path): 258*9c5db199SXin Li names.append(filename) 259*9c5db199SXin Li 260*9c5db199SXin Li for name in names: 261*9c5db199SXin Li pname = parent + '.' + name 262*9c5db199SXin Li importlib.import_module(pname) 263*9c5db199SXin Li if name != 'autotest_lib': 264*9c5db199SXin Li sys.modules[name] = sys.modules[pname] 265