From fcdef2d96cdb51ac59cbca0b33f898916c04aed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sat, 11 Apr 2020 18:11:24 +0200 Subject: [PATCH] plugins: Add dependencies support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- poezio/core/core.py | 4 ++-- poezio/plugin.py | 6 ++++++ poezio/plugin_manager.py | 45 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/poezio/core/core.py b/poezio/core/core.py index 525d02a6..14852ac2 100644 --- a/poezio/core/core.py +++ b/poezio/core/core.py @@ -518,10 +518,10 @@ class Core: plugins = config.get('plugins_autoload') if ':' in plugins: for plugin in plugins.split(':'): - self.plugin_manager.load(plugin) + self.plugin_manager.load(plugin, unload_first=False) else: for plugin in plugins.split(): - self.plugin_manager.load(plugin) + self.plugin_manager.load(plugin, unload_first=False) self.plugins_autoloaded = True def start(self): diff --git a/poezio/plugin.py b/poezio/plugin.py index 61e0ea87..81c849b4 100644 --- a/poezio/plugin.py +++ b/poezio/plugin.py @@ -3,6 +3,8 @@ Define the PluginConfig and Plugin classes, plus the SafetyMetaclass. These are used in the plugin system added in poezio 0.7.5 (see plugin_manager.py) """ + +from typing import Set from asyncio import iscoroutinefunction from functools import partial from configparser import RawConfigParser @@ -399,7 +401,11 @@ class BasePlugin(object, metaclass=SafetyMetaclass): Class that all plugins derive from. """ + # Internal use only + _unloading = False + default_config = None + dependencies: Set[str] = set() def __init__(self, name, plugin_api, core, plugins_conf_dir): self.__name = name diff --git a/poezio/plugin_manager.py b/poezio/plugin_manager.py index 75a6b4a3..c44a8ecc 100644 --- a/poezio/plugin_manager.py +++ b/poezio/plugin_manager.py @@ -7,6 +7,7 @@ plugin env. import logging import os +from typing import Dict, Set from importlib import import_module, machinery from pathlib import Path from os import path @@ -27,6 +28,8 @@ class PluginManager: And keeps track of everything the plugin has done through the API. """ + rdeps: Dict[str, Set[str]] = {} + def __init__(self, core): self.core = core # module name -> module object @@ -58,10 +61,25 @@ class PluginManager: for plugin in set(self.plugins.keys()): self.unload(plugin, notify=False) - def load(self, name: str, notify=True): + def set_rdeps(self, name): + """ + Runs through plugin dependencies to build the reverse dependencies table. + """ + + if name not in self.rdeps: + self.rdeps[name] = set() + for dep in self.plugins[name].dependencies: + if dep not in self.rdeps: + self.rdeps[dep] = {name} + else: + self.rdeps[dep].add(name) + + def load(self, name: str, notify=True, unload_first=True): """ Load a plugin. """ + if not unload_first and name in self.plugins: + return None if name in self.plugins: self.unload(name) @@ -109,8 +127,20 @@ class PluginManager: self.event_handlers[name] = [] try: self.plugins[name] = None + + for dep in module.Plugin.dependencies: + self.load(dep, unload_first=False) + if dep not in self.plugins: + log.debug( + 'Plugin %s couldn\'t load because of dependency %s', + name, dep + ) + return None + self.plugins[name] = module.Plugin(name, self.plugin_api, self.core, self.plugins_conf_dir) + self.set_rdeps(name) + except Exception as e: log.error('Error while loading the plugin %s', name, exc_info=True) if notify: @@ -122,8 +152,20 @@ class PluginManager: self.core.information('Plugin %s loaded' % name, 'Info') def unload(self, name: str, notify=True): + """ + Unloads plugin as well as plugins depending on it. + """ + if name in self.plugins: try: + self.plugins[name]._unloading = True # Prevents loops + for rdep in self.rdeps[name].copy(): + if rdep in self.plugins and not self.plugins[rdep]._unloading: + self.unload(rdep) + if rdep in self.plugins: + log.debug('Failed to unload reverse dependency %s first.', rdep) + return None + for command in self.commands[name].keys(): del self.core.commands[command] for key in self.keys[name].keys(): @@ -143,6 +185,7 @@ class PluginManager: if self.plugins[name] is not None: self.plugins[name].unload() del self.plugins[name] + del self.rdeps[name] del self.commands[name] del self.keys[name] del self.tab_commands[name]