DNF5 Plugins
This is the way for adding a new command to DNF5, allowing users to simply type
dnf5 new-command
and access the implemented functionality.
Command vs Plugin
The reason why you can’t directly implement a new DNF5 command is the following.
The core commands in dnf5/commands
implement essential package manager
functionality, primarily focused on installing and managing packages. They are
integral to the DNF5 package.
Plugin commands implement additional features that extend the capabilities of DNF5. They are packaged separately for optional installation by users who need these additional features.
Thus, core commands and plugins differ by logic and implementation.
Writing an Active Plugin
A DNF5 plugin comprises one or more commands. Visit the DNF5 Command Template to see how to implement a command.
Note that for plugin purposes, we don’t want to register new commands
in the dnf5/main.cpp
file. Instead, we will implement the dnf5::IPlugin
interface by reusing the existing boilerplate code, as shown below (refer to the
comments in the provided code for the expected input locations):
1#include "template.hpp"
2
3#include <dnf5/iplugin.hpp>
4
5using namespace dnf5;
6
7namespace {
8
9constexpr const char * PLUGIN_NAME{"template"};
10
11constexpr PluginVersion PLUGIN_VERSION{.major = 1, .minor = 0, .micro = 0};
12
13constexpr const char * attrs[]{"author.name", "author.email", "description", nullptr};
14constexpr const char * attrs_value[]{"Fred Fedora", "dummy@email.com", "Plugin description."};
15
16class TemplateCmdPlugin : public IPlugin {
17public:
18 using IPlugin::IPlugin;
19
20 /// Fill in the API version of your plugin.
21 /// This is used to check if the provided plugin API version is compatible with the application's plugin API version.
22 /// MANDATORY to override.
23 PluginAPIVersion get_api_version() const noexcept override { return PLUGIN_API_VERSION; }
24
25 /// Enter the name of your new plugin.
26 /// This is used in log messages when an action or error related to the plugin occurs.
27 /// MANDATORY to override.
28 const char * get_name() const noexcept override { return PLUGIN_NAME; }
29
30 /// Fill in the version of your plugin.
31 /// This is utilized in informative and debugging log messages.
32 /// MANDATORY to override.
33 PluginVersion get_version() const noexcept override { return PLUGIN_VERSION; }
34
35 /// Add custom attributes, such as information about yourself and a description of the plugin.
36 /// These can be used to query plugin-specific data through the API.
37 /// Optional to override.
38 const char * const * get_attributes() const noexcept override { return attrs; }
39 const char * get_attribute(const char * attribute) const noexcept override {
40 for (size_t i = 0; attrs[i]; ++i) {
41 if (std::strcmp(attribute, attrs[i]) == 0) {
42 return attrs_value[i];
43 }
44 }
45 return nullptr;
46 }
47
48 /// Export all the commands that plugin is implementing.
49 /// MANDATORY to override.
50 std::vector<std::unique_ptr<Command>> create_commands() override;
51
52 /// Initialization method called after the Base object is created and before command-line arguments are parsed.
53 /// Optional to override.
54 void init() override {}
55
56 /// Cleanup method called when plugin objects are garbage collected.
57 /// Optional to override.
58 void finish() noexcept override {}
59};
60
61std::vector<std::unique_ptr<Command>> TemplateCmdPlugin::create_commands() {
62 std::vector<std::unique_ptr<Command>> commands;
63 commands.push_back(std::make_unique<TemplateCommand>(get_context()));
64 return commands;
65}
66
67
68} // namespace
69
70/// Below is a block of functions with C linkage used for loading the plugin binaries from disk.
71/// All of these are MANDATORY to implement.
72
73/// Return plugin's API version.
74PluginAPIVersion dnf5_plugin_get_api_version(void) {
75 return PLUGIN_API_VERSION;
76}
77
78/// Return plugin's name.
79const char * dnf5_plugin_get_name(void) {
80 return PLUGIN_NAME;
81}
82
83/// Return plugin's version.
84PluginVersion dnf5_plugin_get_version(void) {
85 return PLUGIN_VERSION;
86}
87
88/// Return the instance of the implemented plugin.
89IPlugin * dnf5_plugin_new_instance([[maybe_unused]] ApplicationVersion application_version, Context & context) try {
90 return new TemplateCmdPlugin(context);
91} catch (...) {
92 return nullptr;
93}
94
95/// Delete the plugin instance.
96void dnf5_plugin_delete_instance(IPlugin * plugin_object) {
97 delete plugin_object;
98}
Note
During the startup of the DNF5 application, the plugin library scans
the files in the configured plugins directory to check for the presence
of any plugins implementing the common dnf5::IPlugin
interface.
If such plugins are found, they are loaded into memory along with all
the commands they implement.
Each plugin source is structured in its own directory within the
dnf5-plugins
folder. See other plugins, such as builddep_plugin
:
$ tree dnf5-plugins/builddep_plugin/
dnf5-plugins/builddep_plugin/
├── builddep_cmd_plugin.cpp
├── builddep.cpp
├── builddep.hpp
└── CMakeLists.txt
Building the Binary
To create the plugin binary, add a CMake build script:
1# set gettext domain for translations
2add_definitions(-DGETTEXT_DOMAIN=\"dnf5_cmd_template\")
3
4# add your source files
5add_library(template_cmd_plugin MODULE template.cpp template_cmd_plugin.cpp)
6
7# disable the 'lib' prefix in order to create template_cmd_plugin.so
8set_target_properties(template_cmd_plugin PROPERTIES PREFIX "")
9
10# optionally, add your dependencies and link them to the plugin
11# pkg_check_modules(RPM REQUIRED rpm)
12# target_link_libraries(template_cmd_plugin PRIVATE ${RPM_LIBRARIES})
13
14# link the default dnf libraries
15target_link_libraries(template_cmd_plugin PRIVATE dnf5 libdnf5 libdnf5-cli)
16
17# install the plugin into the common dnf5-plugins location
18install(TARGETS template_cmd_plugin LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/dnf5/plugins/)
Include the newly created plugin in the CMakeLists.txt
parent file inside
dnf5-plugins
like this: add_subdirectory("template_plugin")
.
Delivering to the User
We still need to set up the deployment process so that users can install the new plugin into DNF5 through the package manager.
Add a new provide in the dnf5-plugins
section of the dnf5.spec
file:
Provides: dnf5-command(template)
.
You should also include a documentation man page describing the usage of your plugin in detail. There are several places which need to be interconnected, make sure to complete all the following steps:
doc/dnf5_plugins/template.8.rst
: Add a new man page for your plugin with the respective name.doc/dnf5_plugins/index.rst
: Reference the new plugin in the DNF5 plugins page.doc/dnf5.8.rst
: Reference the new plugin in the main DNF5 man page.doc/CMakeLists.txt
anddoc/conf.py.in
: Integrate with Sphinx documentation.dnf5.spec
: Include the new man page in thednf5-plugins
package.