Build Your Own: Plugins in Empire
Dakota NelsonNov 30, 2017
Empire now has a new feature: plugins! These allow for a great deal of flexibility and customization if you’re willing to write a little bit of code, and this post is dedicated to helping you get the most out of this new way Empire can be customized to meet your needs.
The start of a plugin system was added in pull request #790 and will be coming soon to an Empire near you. This article will walk through:
How to Use Empire Plugins
You’ll notice the Empire help menu has two new commands; plugin
, and plugins
.
Running the plugins
command will give you a list of all the plugins you have available:
In order to make a plugin available to load, it needs to be present in the plugins
directory - if you’re not sure where to find that, the plugins
command prints it out.
Once you’ve installed a plugin (by placing it in the plugins
directory), you can load it by running plugin $plugin-name
, like you see below:
Plugins are unloaded whenever Empire exits. Right now, that’s the only way to unload them.
Once your plugin is loaded, running the plugins
command will show which plugins are active by putting a row of *
characters under the “active” heading in that plugin’s row:
This particular plugin gives us one new capability - a command avilable from the main menu called test
. Running the command gives you some example output:
Obviously this command isn’t very useful. :)
That’s it for how to use plugins - next up, how do they actually work?
How Empire Plugins Work
Note: all links to code in this section are to the original commit in Github which added plugin functionality, and therefore will not reflect more recent changes.
The core of the plugin system is in two commands added to the main Empire menu (both of which are described above). The plugins command lists all available plugins, and notes which ones are loaded. It does this by calling pkgutil.walk_packages
on the plugins
directory, which recursively yields all modules in that directory, and comparing their names against a list of loaded plugins stored in self.loadedPlugins
(self
here referring to the Empire mainMenu object). The plugin command, meanwhile, starts off the same by using pkgutil.walk_packages
, but instead of printing all of the modules it finds, it merely checks to make sure that the plugin name passed to it exists in the list of plugins it finds. If the plugin named by the user actually exists, it calls plugins.load_plugin, which does a series of things:
- Take the name of a plugin and turn it into a fully-named python import (e.g. going from
examplePlugin
toplugins.examplePlugin
). - Carry out the actual import using importlib.import_module - any code written in the body of the plugin module outside the
Plugin
class will execute here. - Instantiate the
Plugin
class found within the newly-imported module. (see the parent class for that object and the “How to Write a Plugin for Empire” section below for what this class looks like) Any code in theonLoad
function of thePlugin
class will execute here. - Reference that plugin object in
mainMenu.loadedPlugins[pluginName]
so it can be accessed later by any code with access to themainMenu
object.
That’s fundamentally it - from here, it matters what the plugin itself does. The plugin object is in charge of doing any setup work for itself, such as adding new commands to the Empire menu - we’ll dive into how to do that next.
How to Write a Plugin for Empire
A simple example plugin ships with Empire. Let’s figure out how it works.
To start, we’ll go over the basics: any plugin written for Empire MUST have a class named Plugin
, which inherits from lib.common.plugins.Plugin
. Working within that structure, almost anything is possible!
When the plugin is first loaded into Empire, any code you write outside the Plugin
class will execute - for instance, the example plugin has print("Hello from your new plugin!")
, which will run immediately when the user loads the plugin. Note that any code you put here will only run when the module is imported, not when a new plugin instance is created (which, right now, is a useless distinction since each thing is done once, but it’s unclear if that will ever change - see the plugin loading process in “How Empire Plugins Work” above).
Whenever a new instance of your Plugin
object is created, the onLoad
member function will be executed to allow your plugin to do any setup work it needs to do. In the example plugin, this simply prints another welcome message and sets up a variable to be used later - self.calledTimes
, which will persist until the plugin is unloaded (e.g. Empire exits).
After the onLoad
function is done, a critical function is called: register
. This function is passed an instance of the mainMenu
object, which is a core Empire component. Manipulating mainMenu
is what allows you to add functionality to Empire. In the example plugin, we add a new command - test
. This is done by defining a function called do_test
(the command name is taken automatically from the name of the function) and adding it to the mainMenu.__class__
object. This modifies the class of the mainMenu
object, mutating it to include your new command!
From there, it’s simply a matter of writing the custom functionality you want to add - you can import whatever Empire modules and packages you should need (the example imports a helper which prints output in color) to lighten the burden along the way.
If you find any bugs or want to add more features, feel free to open an issue on Github. If you have any questions, or are confused by anything, get in touch and I’ll get you some answers ASAP.
Was this useful? Consider giving us your email below so we can send you more great stuff!