• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2013 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"""The plugin management system for the cr tool.
6
7This holds the Plugin class and supporting code, that controls how plugins are
8found and used.
9The module registers a scan hook with the cr.loader system to enable it to
10discover plugins as they are loaded.
11"""
12from operator import attrgetter
13
14import cr
15import cr.loader
16
17
18def _PluginConfig(name, only_enabled=False, only_active=False):
19  config = cr.Config(name)
20  config.only_active = only_active
21  config.only_enabled = only_enabled or config.only_active
22  config.property_name = name.lower() + '_config'
23  return config
24
25_selectors = cr.Config('PRIORITY')
26CONFIG_TYPES = [
27    # Lowest priority, always there default values.
28    _PluginConfig('DEFAULT').AddChild(_selectors),
29    # Only turned on if the plugin is enabled.
30    _PluginConfig('ENABLED', only_enabled=True),
31    # Only turned on while the plugin is the active one.
32    _PluginConfig('ACTIVE', only_active=True),
33    # Holds detected values for active plugins.
34    _PluginConfig('DETECTED', only_active=True),
35    # Holds overrides, used in custom setup plugins.
36    _PluginConfig('OVERRIDES'),
37]
38
39cr.config.GLOBALS.extend(CONFIG_TYPES)
40_plugins = {}
41
42
43# Actually a decorator, so pylint: disable=invalid-name
44class classproperty(object):
45  """This adds a property to a class.
46
47  This is like a simple form of @property except it is for the class, rather
48  than instances of the class. Only supports readonly properties.
49  """
50
51  def __init__(self, getter):
52    self.getter = getter
53
54  def __get__(self, instance, owner):
55    return self.getter(owner)
56
57
58class DynamicChoices(object):
59  """Manages the list of active plugins for command line options.
60
61  Looks like a simple iterable, but it can change as the underlying plugins
62  arrive and enable/disable themselves. This allows it to be used as the
63  set of valid choices for the argparse command line options.
64  """
65
66  # If this is True, all DynamicChoices only return active plugins.
67  # If false, all plugins are included.
68  only_active = True
69
70  def __init__(self, cls):
71    self.cls = cls
72
73  def __contains__(self, name):
74    return self.cls.FindPlugin(name, self.only_active) is not None
75
76  def __iter__(self):
77    return [p.name for p in self.cls.Plugins()].__iter__()
78
79
80def _FindRoot(cls):
81  if Plugin.Type in cls.__bases__:
82    return cls
83  for base in cls.__bases__:
84    result = _FindRoot(base)
85    if result is not None:
86      return result
87  return None
88
89
90class Plugin(cr.loader.AutoExport):
91  """Base class for managing registered plugin types."""
92
93  class Type(object):
94    """Base class that tags a class as an abstract plugin type."""
95
96  class activemethod(object):
97    """A decorator that delegates a static method to the active plugin.
98
99    Makes a static method that delegates to the equivalent method on the
100    active instance of the plugin type.
101    """
102
103    def __init__(self, method):
104      self.method = method
105
106    def __get__(self, instance, owner):
107      def unbound(*args, **kwargs):
108        active = owner.GetActivePlugin()
109        if not active:
110          print 'No active', owner.__name__
111          exit(1)
112        method = getattr(active, self.method.__name__, None)
113        if not method:
114          print owner.__name__, 'does not support', self.method.__name__
115          exit(1)
116        return method(*args, **kwargs)
117
118      def bound(*args, **kwargs):
119        return self.method(instance, *args, **kwargs)
120
121      if instance is None:
122        return unbound
123      return bound
124
125  def __init__(self):
126    # Default the name to the lowercased class name.
127    self._name = self.__class__.__name__.lower()
128    # Strip the common suffix if present.
129    self._root = _FindRoot(self.__class__)
130    rootname = self._root.__name__.lower()
131    if self._name.endswith(rootname) and self.__class__ != self._root:
132      self._name = self._name[:-len(rootname)]
133    for config_root in CONFIG_TYPES:
134      config = cr.Config()
135      setattr(self, config_root.property_name, config)
136    self._is_active = False
137
138  def Init(self):
139    """Post plugin registration initialisation method."""
140    for config_root in CONFIG_TYPES:
141      config = getattr(self, config_root.property_name)
142      config.name = self.name
143      if config_root.only_active and not self.is_active:
144        config.enabled = False
145      if config_root.only_enabled and not self.enabled:
146        config.enabled = False
147      child = getattr(self.__class__, config_root.name, None)
148      if child is not None:
149        child.name = self.__class__.__name__
150        config.AddChild(child)
151      config_root.AddChild(config)
152
153  @property
154  def name(self):
155    return self._name
156
157  @property
158  def priority(self):
159    return 0
160
161  @property
162  def enabled(self):
163    # By default all non type classes are enabled.
164    return Plugin.Type not in self.__class__.__bases__
165
166  @property
167  def is_active(self):
168    return self._is_active
169
170  def Activate(self):
171    assert not self._is_active
172    self._is_active = True
173    for config_root in CONFIG_TYPES:
174      if config_root.only_active:
175        getattr(self, config_root.property_name).enabled = True
176
177  def Deactivate(self):
178    assert self._is_active
179    self._is_active = False
180    for config_root in CONFIG_TYPES:
181      if config_root.only_active:
182        getattr(self, config_root.property_name).enabled = False
183
184  @classmethod
185  def ClassInit(cls):
186    pass
187
188  @classmethod
189  def GetInstance(cls):
190    """Gets an instance of this plugin.
191
192    This looks in the plugin registry, and if an instance is not found a new
193    one is built and registered.
194
195    Returns:
196        The registered plugin instance.
197    """
198    plugin = _plugins.get(cls, None)
199    if plugin is None:
200      # Run delayed class initialization
201      cls.ClassInit()
202      # Build a new instance of cls, and register it as the main instance.
203      plugin = cls()
204      _plugins[cls] = plugin
205      # Wire up the hierarchy for Config objects.
206      for name, value in cls.__dict__.items():
207        if isinstance(value, cr.Config):
208          for base in cls.__bases__:
209            child = getattr(base, name, None)
210            if child is not None:
211              value.AddChild(child)
212      plugin.Init()
213    return plugin
214
215  @classmethod
216  def AllPlugins(cls):
217    # Don't yield abstract roots, just children. We detect roots as direct
218    # sub classes of Plugin.Type
219    if Plugin.Type not in cls.__bases__:
220      yield cls.GetInstance()
221    for child in cls.__subclasses__():
222      for p in child.AllPlugins():
223        yield p
224
225  @classmethod
226  def UnorderedPlugins(cls):
227    """Returns all enabled plugins of type cls, in undefined order."""
228    plugin = cls.GetInstance()
229    if plugin.enabled:
230      yield plugin
231    for child in cls.__subclasses__():
232      for p in child.UnorderedPlugins():
233        yield p
234
235  @classmethod
236  def Plugins(cls):
237    """Return all enabled plugins of type cls in priority order."""
238    return sorted(cls.UnorderedPlugins(),
239                  key=attrgetter('priority'), reverse=True)
240
241  @classmethod
242  def Choices(cls):
243    return DynamicChoices(cls)
244
245  @classmethod
246  def FindPlugin(cls, name, only_active=True):
247    if only_active:
248      plugins = cls.UnorderedPlugins()
249    else:
250      plugins = cls.AllPlugins()
251    for plugin in plugins:
252      if plugin.name == name or plugin.__class__.__name__ == name:
253        return plugin
254    return None
255
256  @classmethod
257  def GetPlugin(cls, name):
258    result = cls.FindPlugin(name)
259    if result is None:
260      raise KeyError(name)
261    return result
262
263  @classmethod
264  def GetAllActive(cls):
265    return [plugin for plugin in cls.UnorderedPlugins() if plugin.is_active]
266
267  @classmethod
268  def GetActivePlugin(cls):
269    """Gets the active plugin of type cls.
270
271    This method will select a plugin to be the active one, and will activate
272    the plugin if needed.
273    Returns:
274      the plugin that is currently active.
275    """
276    plugin, _ = _GetActivePlugin(cls)
277    return plugin
278
279  @classproperty
280  def default(cls):
281    """Returns the plugin that should be used if the user did not choose one."""
282    result = None
283    for plugin in cls.UnorderedPlugins():
284      if not result or plugin.priority > result.priority:
285        result = plugin
286    return result
287
288  @classmethod
289  def Select(cls):
290    """Called to determine which plugin should be the active one."""
291    plugin = cls.default
292    selector = getattr(cls, 'SELECTOR', None)
293    if selector:
294      if plugin is not None:
295        _selectors[selector] = plugin.name
296      name = cr.context.Find(selector)
297      if name is not None:
298        plugin = cls.FindPlugin(name)
299    return plugin
300
301
302def ChainModuleConfigs(module):
303  """Detects and connects the default Config objects from a module."""
304  for config_root in CONFIG_TYPES:
305    if hasattr(module, config_root.name):
306      config = getattr(module, config_root.name)
307      config.name = module.__name__
308      config_root.AddChild(config)
309
310
311cr.loader.scan_hooks.append(ChainModuleConfigs)
312
313
314def _GetActivePlugin(cls):
315  activated = False
316  actives = cls.GetAllActive()
317  plugin = cls.Select()
318  for active in actives:
319    if active != plugin:
320      active.Deactivate()
321  if plugin and not plugin.is_active:
322    activated = True
323    plugin.Activate()
324  return plugin, activated
325
326
327def Activate():
328  """Activates a plugin for all known plugin types."""
329  types = Plugin.Type.__subclasses__()
330  modified = True
331  while modified:
332    modified = False
333    for child in types:
334      _, activated = _GetActivePlugin(child)
335      if activated:
336        modified = True
337