Dnf5 command template seems to have bugs

I been trying to create a custom command for dnf5 and following this guide exactly as is 1. DNF5 Command Template — dnf5 documentation . This is my tree structure . I didn’t modify the files at all they provided as example.

└── dnf5
    ├── build
    ├── CMakeLists.txt
    ├── command
    │   ├── arguments.hpp
    │   ├── template.cpp
    │   └── template.hpp
    └── main.cpp

4 directories, 5 files

the Cmake file :

cmake_minimum_required(VERSION 3.20)

# Project name and version
project(MyDNF5Command
    VERSION 1.0
    DESCRIPTION "Custom DNF5 Command "
    LANGUAGES CXX
)

# Set C++ standard and required features
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Set gettext domain for translations
add_definitions(-DGETTEXT_DOMAIN=\"dnf5_cmd_template\")

# Define sources and headers
set(SOURCES
    main.cpp
    command/template.cpp
    command/template.hpp
    command/arguments.hpp
)

# Add the library
add_library(template_cmd_plugin MODULE ${SOURCES})

# Disable the 'lib' prefix to create template_cmd_plugin.so
set_target_properties(template_cmd_plugin PROPERTIES PREFIX "")

# Find and link required packages
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBDNF5 REQUIRED libdnf5)
pkg_check_modules(LIBDNF5_CLI REQUIRED libdnf5-cli)

# Include directories
include_directories(${LIBDNF5_INCLUDE_DIRS} ${LIBDNF5_CLI_INCLUDE_DIRS})

# Link the default dnf libraries
target_link_libraries(template_cmd_plugin PRIVATE ${LIBDNF5_LIBRARIES} ${LIBDNF5_CLI_LIBRARIES})

# Install the plugin into the common dnf5-plugins location
install(TARGETS template_cmd_plugin LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/dnf5/plugins/)

When I build this is the Error :

-- The CXX compiler identification is GNU 14.2.1
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found PkgConfig: /usr/bin/pkg-config (found version "2.3.0")
-- Checking for module 'libdnf5'
--   Found libdnf5, version 5.2.8.1
-- Checking for module 'libdnf5-cli'
--   Found libdnf5-cli, version 5.2.8.1
-- Configuring done (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: h2o/dnf5/build
❯ cmake --build .
[ 33%] Building CXX object CMakeFiles/template_cmd_plugin.dir/main.cpp.o
In file included from h2o/dnf5/command/template.hpp:3,
                 from h2o/dnf5/main.cpp:2:
h2o/dnf5/command/arguments.hpp:38:2: error: #endif without #if
   38 | #endif  // DNF_COMMANDS_DOWNLOAD_TEMPLATE_ARGUMENTS_HPP
      |  ^~~~~
h2o/dnf5/command/template.hpp:90:2: error: #endif without #if
   90 | #endif  // DNF5_COMMANDS_TEMPLATE_TEMPLATE_HPP
      |  ^~~~~
h2o/dnf5/command/template.hpp: In constructor ‘dnf5::TemplateCommand::TemplateCommand(dnf5::Command&)’:
h2o/dnf5/command/template.hpp:31:76: error: no matching function for call to ‘dnf5::Command::Command(dnf5::Command&, const char [9])’
   31 |     explicit TemplateCommand(Command & parent) : Command(parent, "template") {}
      |                                                                            ^
In file included from /home/mahalo/repos/dnf-purge-command/h2o/dnf5/command/arguments.hpp:4:
/usr/include/libdnf5-cli/session.hpp:91:14: note: candidate: ‘libdnf5::cli::session::Command::Command(libdnf5::cli::session::Session&, const std::string&)’
   91 |     explicit Command(Session & session, const std::string & name);
      |              ^~~~~~~
In file included from /home/mahalo/repos/dnf-purge-command/h2o/dnf5/command/template.hpp:5:
/usr/include/dnf5/context.hpp:183:43: note:   inherited here
  183 |     using libdnf5::cli::session::Command::Command;
      |                                           ^~~~~~~
/usr/include/libdnf5-cli/session.hpp:91:32: note:   no known conversion for argument 1 from ‘dnf5::Command’ to ‘libdnf5::cli::session::Session&’
   91 |     explicit Command(Session & session, const std::string & name);
      |                      ~~~~~~~~~~^~~~~~~
/h2o/dnf5/main.cpp: At global scope:
/h2o/dnf5/main.cpp:5:38: error: ‘TemplateCommand’ was not declared in this scope; did you mean ‘dnf5::TemplateCommand’?
    5 | register_subcommand(std::make_unique<TemplateCommand>(*this), software_management_commands_group);
      |                                      ^~~~~~~~~~~~~~~
      |                                      dnf5::TemplateCommand
h2o/dnf5/command/template.hpp:25:7: note: ‘dnf5::TemplateCommand’ declared here
   25 | class TemplateCommand : public Command {
      |       ^~~~~~~~~~~~~~~
h2o/dnf5/main.cpp:5:20: error: expected constructor, destructor, or type conversion before ‘(’ token
    5 | register_subcommand(std::make_unique<TemplateCommand>(*this), software_management_commands_group);
      |                    ^
gmake[2]: *** [CMakeFiles/template_cmd_plugin.dir/build.make:76: CMakeFiles/template_cmd_plugin.dir/main.cpp.o] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:83: CMakeFiles/template_cmd_plugin.dir/all] Error 2
gmake: *** [Makefile:136: all] Error 2

Suggest you raise an issue here GitHub - rpm-software-management/dnf5: Next-generation RPM package management system
Also see if they have updated docs in git repo?

Looking at these error messages, are the header files using #pragma once for guards?

If you’re using #define guards, then each header would start with (for example)

#ifndef DNF_COMMANDS_TEMPLATE_TEMPLATE_HPP
#define DNF_COMMANDS_TEMPLATE_TEMPLATE_HPP

…and end with an #endif.

If you’re using #pragma once, that would be at the top of each file, and there would be no #ifndef, #define, OR #endif to implement the guards.

It looks like, from those error messages, that the top of the file may have been converted to #pragma once, but the #endif at the bottom was left in.

If that’s the case (if there’s a #pragma once at the top of each header) just remove the line with the corresponding #endif (that’s even helpfully commented with the name of the guard symbol) at the bottom.

(And if that is [one of] the issue(s), and it’s present in the template, then submitting a PR to fix the template headers would surely be appreciated.)

Looking at this one, you may just need a #include <string> at the top of your template.hpp, in order for the conversion from a char* to std::string to be recognized. Or (if it’s already there), you could try being explicit about the string construction, by changing:

explicit TemplateCommand(Command & parent) : Command(parent, "template") {}

to

explicit TemplateCommand(Command & parent)
    : Command(parent, std::string("template")) {}

…but normally that shouldn’t be necessary, so it’s probably a missing #include.

With this one, the error message is telling you: You need to refer to TemplateCommand with its namespace from the global scope, so change:

register_subcommand(std::make_unique<TemplateCommand>(*this), software_management_commands_group);

to

register_subcommand(std::make_unique<dnf5::TemplateCommand>(*this), software_management_commands_group);

Or, you can put a using dnf5::TemplateCommand; somewhere before that call, in main.cpp.

I made all the suggested changes and still get this error

[ 33%] Building CXX object CMakeFiles/template_cmd_plugin.dir/main.cpp.o
In file included fromh2o/dnf5/main.cpp:2:
h2o/dnf5/command/template.hpp: In constructor ‘dnf5::TemplateCommand::TemplateCommand(dnf5::Command&)’:
h2o/dnf5/command/template.hpp:21:76: error: no matching function for call to ‘dnf5::Command::Command(dnf5::Command&, const char [9])’
   21 |     explicit TemplateCommand(Command & parent) : Command(parent, "template") {}
      |                                                                            ^
In file included from h2o/dnf5/command/arguments.hpp:5,
                 from h2o/dnf5/command/template.hpp:4:
/usr/include/libdnf5-cli/session.hpp:91:14: note: candidate: ‘libdnf5::cli::session::Command::Command(libdnf5::cli::session::Session&, const std::string&)’
   91 |     explicit Command(Session & session, const std::string & name);
      |              ^~~~~~~
In file included from h2o/dnf5/command/template.hpp:6:
/usr/include/dnf5/context.hpp:183:43: note:   inherited here
  183 |     using libdnf5::cli::session::Command::Command;
      |                                           ^~~~~~~
/usr/include/libdnf5-cli/session.hpp:91:32: note:   no known conversion for argument 1 from ‘dnf5::Command’ to ‘libdnf5::cli::session::Session&’
   91 |     explicit Command(Session & session, const std::string & name);
      |                      ~~~~~~~~~~^~~~~~~
h2o/dnf5/main.cpp: At global scope:
h2o/dnf5/main.cpp:4:20: error: expected constructor, destructor, or type conversion before ‘(’ token
    4 | register_subcommand(std::make_unique<dnf5::TemplateCommand>(*this), software_management_commands_group);
      |                    ^
gmake[2]: *** [CMakeFiles/template_cmd_plugin.dir/build.make:76: CMakeFiles/template_cmd_plugin.dir/main.cpp.o] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:83: CMakeFiles/template_cmd_plugin.dir/all] Error 2
gmake: *** [Makefile:136: all] Error 2`Preformatted text`

For the first one, is it still an error if you use this form?

explicit TemplateCommand(Command & parent)
    : Command(parent, std::string("template")) {}

Oh, wait, no, never mind — my apologies, I misread the initial error. It’s not complaining about the std::string, it’s complaining about the other argument.

Your TemplateCommand constructor takes a Command & parent and passes it to libdnf5::cli::session::Command::Command, but that constructor expects a Session & as its first argument, not a Command &.

So either your constructor definition should be:

explicit TemplateCommand(Session & session) : Command(session, "template") {}

…or…

explicit TemplateCommand(Command & parent) : Command(parent.session, "template") {}

(I strongly suspect it’s the first one, because otherwise you’re effectively losing the value of parent by not storing it anywhere, which seems… unlikely. So I think the TemplateCommand(Command & parent) constructor would have to be more complex, in that case. But we’re kind of veering into guesswork now.)


For the second, I think we’d have to see the full contents of your main.cpp. The first of the two errors there was eliminated, but the second one is impossible to diagnose without context.

Aha! Neither. Looking at the actual download command definition in dnf5/commands/download.hpp, it should actually be;

explicit TemplateCommand(Context & context) : Command(context, "template") {}

But, yeah, that template page in the docs is a mess. I assume it’s fairly outdated.

As for the other, looking at dnf5/main.cpp, commands are actually initialized with a call like:

context.add_and_initialize_command(std::make_unique<TemplateCommand>(context));

in the add_commands(Context & context) function, after adding a #include for your new command’s header at the top of the file.

…I’ll put together a PR to try and shore up the documentation, since this stated goal is not being met at all:

Note

This code is thought to be self explanatory. You should be able to copy the snippets, following the directory structure and naming shown above each one.

…It’s also worth pointing out: The documentation as written there is for adding a command to dnf5 itself, meaning it would be in the dnf5 repo itself. If you’re looking to write an external command defined in a plugin, that’s an entirely different beastie (and is documented in the next set of templates at 2. DNF5 Plugin Template — dnf5 documentation)

…That documentation may also be wrong/outdated, but the point is that main.cpp as discussed in the first set of templates is the actual DNF5 main.cpp, not a separate file for an external plugin. Plugins don’t use register_subcommand or context.add_and_initialize_command or anything like that, as they’re not part of the dnf5 codebase itself.

(That bit is actually documented at Writing an Active Plugin: )

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

For how to write a plugin, a good approach is to use the source of an existing plugin (that’s built as part of dnf5 and still works, meaning the code is up to date) as a template — like dnf5-plugins/builddep_plugin.

I’ve just submitted updates to the code / documentation which will make them fully compatible with the current DNF5 API / codebase.