blob: 3e93cea09bd68adfb306a8ebb4b109978c0ca991 [file] [log] [blame]
Allen Li464220f2017-09-12 17:14:22 -07001# Copyright 2017 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Kludges to support legacy Autotest code.
6
Allen Libca5d012017-10-20 13:44:36 -07007Autotest imports should be done by calling patch() first and then
8calling load(). patch() should only be called once from a script's main
9function.
Allen Li0459da32017-11-10 12:11:42 -080010
11chromite imports should be done with chromite_load(), and any third
12party packages should be imported with deps_load(). The reason for this
13is to present a clear API for these unsafe imports, making it easier to
14identify which imports are currently unsafe. Eventually, everything
15should be moved to virtualenv, but that will not be in the near future.
Allen Li464220f2017-09-12 17:14:22 -070016"""
17
18from __future__ import absolute_import
19from __future__ import division
20from __future__ import print_function
21
22import imp
23import importlib
24import logging
25import os
26import site
27import sys
28
29import autotest_lib
30
31_AUTOTEST_DIR = autotest_lib.__path__[0]
32_SITEPKG_DIR = os.path.join(_AUTOTEST_DIR, 'site-packages')
33
34_setup_done = False
35
36logger = logging.getLogger(__name__)
37
38
Allen Libca5d012017-10-20 13:44:36 -070039def patch():
40 """Monkeypatch everything needed to get Autotest working.
41
42 This should be called before any calls to load(). Only the main
43 function in scripts should call this function.
44
45 Unlike monkeypatch() which is more low-level, this patches not just
46 imports, but also other things in Autotest what would generally be
47 needed to use it.
48 """
49 monkeypatch()
50
Allen Lif4b62ae2017-11-09 15:48:05 -080051 # Add chromite's third-party to the import path.
52 import chromite
53
Allen Libca5d012017-10-20 13:44:36 -070054 # Needed to set up Django environment variables.
55 load('frontend.setup_django_environment')
56
57 # Monkey patch package paths that Django uses to be absolute.
58 settings = load('frontend.settings')
59 settings.INSTALLED_APPS = (
60 'autotest_lib.frontend.afe',
61 'autotest_lib.frontend.tko',
62 'django.contrib.admin',
63 'django.contrib.auth',
64 'django.contrib.contenttypes',
65 'django.contrib.sessions',
66 'django.contrib.sites',
67 )
68
Allen Lif4b62ae2017-11-09 15:48:05 -080069 # drone_utility uses this
70 common = load('scheduler.common')
71 common.autotest_dir = _AUTOTEST_DIR
72
Allen Libca5d012017-10-20 13:44:36 -070073
Allen Li464220f2017-09-12 17:14:22 -070074def monkeypatch():
Allen Libca5d012017-10-20 13:44:36 -070075 """Monkeypatch Autotest imports.
Allen Li464220f2017-09-12 17:14:22 -070076
Allen Li2678d362017-10-11 16:59:07 -070077 This should be called before any calls to load(). Only the main
78 function in scripts should call this function.
Allen Li464220f2017-09-12 17:14:22 -070079
80 This should be called no more than once.
81
Allen Li2678d362017-10-11 16:59:07 -070082 This adds Autotest's site-packages to the import path and modifies
83 sys.meta_path so that all common.py imports are no-ops.
Allen Li464220f2017-09-12 17:14:22 -070084 """
85 global _setup_done
86 assert not _setup_done
87 site.addsitedir(_SITEPKG_DIR)
88 sys.meta_path.insert(0, _CommonRemovingFinder())
89 _setup_done = True
90
91
92class _CommonRemovingFinder(object):
93 """Python import finder that neuters Autotest's common.py
94
95 The common module is replaced with an empty module everywhere it is
96 imported. common.py should have only been imported for side
97 effects, so nothing should actually use the imported module.
98
99 See also https://www.python.org/dev/peps/pep-0302/
100 """
101
102 def find_module(self, fullname, path=None):
103 """Find module."""
104 del path # unused
105 if not self._is_autotest_common(fullname):
106 return None
107 logger.debug('Dummying out %s import', fullname)
108 return self
109
110 def _is_autotest_common(self, fullname):
111 return (fullname.partition('.')[0] == 'autotest_lib'
112 and fullname.rpartition('.')[-1] == 'common')
113
114 def load_module(self, fullname):
115 """Load module."""
Allen Libca5d012017-10-20 13:44:36 -0700116 if fullname in sys.modules: # pragma: no cover
Allen Li464220f2017-09-12 17:14:22 -0700117 return sys.modules[fullname]
118 mod = imp.new_module(fullname)
119 mod.__file__ = '<removed>'
120 mod.__loader__ = self
121 mod.__package__ = fullname.rpartition('.')[0]
122 sys.modules[fullname] = mod
123 return mod
124
125
126def load(name):
127 """Import module from autotest.
128
Allen Li2678d362017-10-11 16:59:07 -0700129 This enforces that monkeypatch() is called first.
Allen Licc153502017-09-15 16:08:25 -0700130
131 @param name: name of module as string, e.g., 'frontend.afe.models'
Allen Li464220f2017-09-12 17:14:22 -0700132 """
Allen Li0459da32017-11-10 12:11:42 -0800133 return _load('autotest_lib.%s' % name)
134
135
136def chromite_load(name):
137 """Import module from chromite.lib.
138
139 This enforces that monkeypatch() is called first.
140
141 @param name: name of module as string, e.g., 'metrics'
142 """
143 return _load('chromite.lib.%s' % name)
144
145
146def deps_load(name):
147 """Import module from chromite.lib.
148
149 This enforces that monkeypatch() is called first.
150
151 @param name: name of module as string, e.g., 'metrics'
152 """
153 assert not name.startswith('autotest_lib')
154 assert not name.startswith('chromite.lib')
155 return _load(name)
156
157
158def _load(name):
159 """Import a module.
160
161 This enforces that monkeypatch() is called first.
162
163 @param name: name of module as string
164 """
Allen Li464220f2017-09-12 17:14:22 -0700165 if not _setup_done:
Allen Li0459da32017-11-10 12:11:42 -0800166 raise ImportError('cannot load chromite modules before monkeypatching')
167 return importlib.import_module(name)