import os import re import six import sys # This must run on Python versions less than 2.4. dirname = os.path.dirname(sys.modules[__name__].__file__) common_dir = os.path.abspath(os.path.join(dirname, 'common_lib')) sys.path.insert(0, common_dir) import check_version sys.path.pop(0) def _get_pyversion_from_args(): """Extract, format, & pop the current py_version from args, if provided.""" py_version = 3 py_version_re = re.compile(r'--py_version=(\w+)\b') version_found = False for i, arg in enumerate(sys.argv): if not arg.startswith('--py_version'): continue result = py_version_re.search(arg) if result: if version_found: raise ValueError('--py_version may only be specified once.') py_version = result.group(1) version_found = True if py_version not in ('2', '3'): raise ValueError('Python version must be "2" or "3".') # Remove the arg so other argparsers don't get grumpy. sys.argv.pop(i) return py_version def _desired_version(): """ Returns desired python version. If the PY_VERSION env var is set, just return that. This is the case when autoserv kicks of autotest on the server side via a job.run(), or a process created a subprocess. Otherwise, parse & pop the sys.argv for the '--py_version' flag. If no flag is set, default to python 3. """ # Even if the arg is in the env vars, we will attempt to get it from the # args, so that it can be popped prior to other argparsers hitting. py_version = _get_pyversion_from_args() if os.getenv('PY_VERSION'): return int(os.getenv('PY_VERSION')) os.environ['PY_VERSION'] = str(py_version) return int(py_version) desired_version = _desired_version() if desired_version == sys.version_info.major: os.environ['AUTOTEST_NO_RESTART'] = 'True' else: # There are cases were this can be set (ie by test_that), but a subprocess # is launched in the incorrect version. if os.getenv('AUTOTEST_NO_RESTART'): del os.environ['AUTOTEST_NO_RESTART'] check_version.check_python_version(desired_version) import glob, traceback def import_module(module, from_where): """Equivalent to 'from from_where import module' Returns the corresponding module""" from_module = __import__(from_where, globals(), locals(), [module]) return getattr(from_module, module) def _autotest_logging_handle_error(self, record): """Method to monkey patch into logging.Handler to replace handleError.""" # The same as the default logging.Handler.handleError but also prints # out the original record causing the error so there is -some- idea # about which call caused the logging error. import logging if logging.raiseExceptions: # Avoid recursion as the below output can end up back in here when # something has *seriously* gone wrong in autotest. logging.raiseExceptions = 0 sys.stderr.write('Exception occurred formatting message: ' '%r using args %r\n' % (record.msg, record.args)) traceback.print_stack() sys.stderr.write('-' * 50 + '\n') traceback.print_exc() sys.stderr.write('Future logging formatting exceptions disabled.\n') def _monkeypatch_logging_handle_error(): # Hack out logging.py* logging_py = os.path.join(os.path.dirname(__file__), 'common_lib', 'logging.py*') if glob.glob(logging_py): os.system('rm -f %s' % logging_py) # Monkey patch our own handleError into the logging module's StreamHandler. # A nicer way of doing this -might- be to have our own logging module define # an autotest Logger instance that added our own Handler subclass with this # handleError method in it. But that would mean modifying tons of code. import logging assert callable(logging.Handler.handleError) logging.Handler.handleError = _autotest_logging_handle_error def _insert_site_packages(root): # Allow locally installed third party packages to be found # before any that are installed on the system itself when not. # running as a client. # This is primarily for the benefit of frontend and tko so that they # may use libraries other than those available as system packages. if six.PY2: sys.path.insert(0, os.path.join(root, 'site-packages')) import importlib ROOT_MODULE_NAME_ALLOW_LIST = ( 'autotest_lib', 'autotest_lib.client', ) def _setup_top_level_symlink(base_path): """Create a self pointing symlink in the base_path).""" if os.path.islink(os.path.join(base_path, 'autotest_lib')): return os.chdir(base_path) os.symlink('.', 'autotest_lib') def _setup_client_symlink(base_path): """Setup the client symlink for the DUT. Creates a "autotest_lib" folder in client, then creates a symlink called "client" pointing back to ../, as well as an __init__ for the folder. """ def _create_client_symlink(): os.chdir(autotest_lib_dir) with open('__init__.py', 'w'): pass os.symlink('../', 'client') autotest_lib_dir = os.path.join(base_path, 'autotest_lib') link_path = os.path.join(autotest_lib_dir, 'client') # TODO: Use os.makedirs(..., exist_ok=True) after switching to Python 3 if not os.path.isdir(autotest_lib_dir): try: os.mkdir(autotest_lib_dir) except FileExistsError as e: if not os.path.isdir(autotest_lib_dir): raise e if os.path.islink(link_path): return try: _create_client_symlink() # It's possible 2 autotest processes are running at once, and one # creates the symlink in the time between checking and creating. # Thus if the symlink DNE, and we cannot create it, check for its # existence and exit if it exists. except FileExistsError as e: if os.path.islink(link_path): return raise e def _symlink_check(base_path, root_dir): """Verify the required symlinks are present, and add them if not.""" # Note the starting cwd to later change back to it. starting_dir = os.getcwd() if root_dir == 'autotest_lib': _setup_top_level_symlink(base_path) elif root_dir == 'autotest_lib.client': _setup_client_symlink(base_path) os.chdir(starting_dir) def setup(base_path, root_module_name): _symlink_check(base_path, root_module_name) if root_module_name not in ROOT_MODULE_NAME_ALLOW_LIST: raise Exception('Unexpected root module: ' + root_module_name) _insert_site_packages(base_path) # Ie, server (or just not /client) if root_module_name == 'autotest_lib': # Base path is just x/x/x/x/autotest/files _setup_autotest_lib(base_path) _preimport_top_level_packages(os.path.join(base_path, 'autotest_lib'), parent='autotest_lib') else: # aka, in /client/ if os.path.exists(os.path.join(os.path.dirname(base_path), 'server')): # Takes you from /client/ to /files # this is because on DUT there is no files/client autotest_base_path = os.path.dirname(base_path) else: autotest_base_path = base_path _setup_autotest_lib(autotest_base_path) _preimport_top_level_packages(os.path.join(autotest_base_path, 'autotest_lib'), parent='autotest_lib') _preimport_top_level_packages( os.path.join(autotest_base_path, 'autotest_lib', 'client'), parent='autotest_lib.client', ) _monkeypatch_logging_handle_error() def _setup_autotest_lib(path): sys.path.insert(0, path) # This is a symlink back to the root directory, that does all the magic. importlib.import_module('autotest_lib') sys.path.pop(0) def _preimport_top_level_packages(root, parent): # The old code to setup the packages used to fetch the top-level packages # inside autotest_lib. We keep that behaviour in order to avoid having to # add import statements for the top-level packages all over the codebase. # # e.g., # import common # from autotest_lib.server import utils # # must continue to work. The _right_ way to do that import would be. # # import common # import autotest_lib.server # from autotest_lib.server import utils names = [] for filename in os.listdir(root): path = os.path.join(root, filename) if not os.path.isdir(path): continue # skip files if '.' in filename: continue # if "." is in the name it's not a valid package name if not os.access(path, os.R_OK | os.X_OK): continue # need read + exec access to make a dir importable if '__init__.py' in os.listdir(path): names.append(filename) for name in names: pname = parent + '.' + name importlib.import_module(pname) if name != 'autotest_lib': sys.modules[name] = sys.modules[pname]