4. Changes in resolving dependencies and running a transaction

In both DNF4 and DNF5, there is a Goal class which allows to add items for resolving into a transaction. Once all the items are added, the Goal can be resolved, which produces a Transaction object to run.

DNF4 also has methods in the dnf.base.Base class to modify the Goal, such as dnf.base.Base.install, dnf.base.Base.remove etc., dnf.base.Base.resolve to resolve the goal and dnf.base.Base.do_transaction to run the transaction. In DNF5, the Goal class is used directly. The libdnf5::Goal::resolve returns the resulting transaction which can be run with libdnf5::base::Transaction::run.

In case of an error in resolving dependencies, in DNF4, the dnf.base.Base.resolve raises dnf.exceptions.DepsolveError and the problems may be acquired using methods of the dnf.goal.Goal class, while in DNF5, the libdnf5::Goal::resolve doesn’t raise any exception and the problems are stored in the returned libdnf5::base::Transaction object.

DNF4 Python:

 1# Add an RPM package named "one" for installation into the goal.
 2base.install("one")
 3
 4try:
 5    # Resolve the goal, create a transaction object.
 6    base.resolve()
 7except dnf.exceptions.DepsolveError as e:
 8    print(e)
 9    raise
10
11# Download the packages.
12progress = dnf.cli.progress.MultiFileProgressMeter()
13base.download_packages(base.transaction.install_set, progress)
14
15# Run the transaction.
16base.do_transaction()

DNF5 Python:

 1# Create a goal.
 2goal = libdnf5.base.Goal(base)
 3
 4# Add an RPM package named "one" for installation into the goal.
 5goal.add_rpm_install("one")
 6
 7# Resolve the goal, create a transaction object.
 8transaction = goal.resolve()
 9
10if transaction.get_problems() != libdnf5.base.GoalProblem_NO_PROBLEM:
11    for message in transaction.get_resolve_logs_as_strings():
12        print(message)
13
14# We can iterate over the resolved transaction and inspect the packages.
15for tspkg in transaction.get_transaction_packages():
16    print(
17        tspkg.get_package().get_nevra(), ": ",
18        libdnf5.base.transaction.transaction_item_action_to_string(
19            tspkg.get_action()
20        )
21    )
22
23# Download the packages.
24transaction.download()
25
26# Run the transaction.
27transaction.run()

DNF5 C++:

 1#include <libdnf5/base/goal.hpp>
 2
 3// Create a goal
 4libdnf5::Goal goal(base);
 5
 6// Add an RPM package named "one" for installation into the goal.
 7goal.add_rpm_install("one");
 8
 9// Resolve the goal, create a transaction object.
10auto transaction = goal.resolve();
11
12if (transaction.get_problems() != libdnf5::GoalProblem::NO_PROBLEM) {
13    for (auto message : transaction.get_resolve_logs_as_strings()) {
14        std::cout << message << std::endl;
15    }
16}
17
18// We can iterate over the resolved transaction and inspect the packages.
19std::cout << "Resolved transaction:" << std::endl;
20for (const auto & tspkg : transaction.get_transaction_packages()) {
21    std::cout << tspkg.get_package().get_nevra() << ": " << transaction_item_action_to_string(tspkg.get_action())
22              << std::endl;
23}
24
25// Download the packages.
26transaction.download();
27
28// Run the transaction.
29transaction.run();

Both DNF4 and DNF5 provide information about the progress of downloads using callbacks. This is implemented by inheriting from a callbacks base class and overriding its methods. The dnf.callback.DownloadProgress class in DNF4 is in DNF5 replaced by the libdnf5::repo::DownloadCallbacks class and the methods are slightly different.

In DNF5, there is a callback method add_new_download for each item to be downloaded instead of the start method for the whole download. The add_new_download returns a pointer to user data (for other language bindings, it’s an integer instead, due to issues with ownership of the data) that are then passed as arguments to the other callback methods, so that they can be linked together. The DNF5 examples show, how it can be used.

DNF4 Python:

 1class PackageDownloadCallbacks(dnf.callback.DownloadProgress):
 2    def start(self, total_files, total_size):
 3        print(f"Started downloading {total_files} files, total size: {total_size}")
 4
 5    def end(self, payload, status, msg):
 6        if status is dnf.callback.STATUS_OK:
 7            print(f"Downloaded '{payload}'")
 8        elif status is dnf.callback.STATUS_ALREADY_EXISTS:
 9            print(f"Skipped to download '{payload}': {msg}")
10        else:
11            print(f"Failed to download '{payload}': {msg}")
12
13# Download the packages.
14base.download_packages(base.transaction.install_set, PackageDownloadCallbacks())

DNF5 Python:

 1class PackageDownloadCallbacks(libdnf5.repo.DownloadCallbacks):
 2    # List of descriptions of downloads.
 3    download_descriptions = []
 4
 5    def add_new_download(self, user_data, description, total_to_download):
 6        print(f"Started downloading: {description}, size: {total_to_download}")
 7
 8        # Store the message describing the new download and return its index in the list.
 9        self.download_descriptions.append(description)
10        return len(self.download_descriptions) - 1
11
12    def end(self, user_cb_data, status, msg):
13        # Get the description based on the index passed in user_cb_data.
14        description = self.download_descriptions[user_cb_data]
15
16        if status is libdnf5.repo.DownloadCallbacks.TransferStatus_SUCCESSFUL:
17            print(f"Downloaded: {description}")
18        elif status is libdnf5.repo.DownloadCallbacks.TransferStatus_ALREADYEXISTS:
19            print(f"Skipped to download: {description}: {msg}")
20        else:
21            print(f"Failed to download: {description}: {msg}")
22
23        return 0
24
25
26# Set the download callbacks.
27downloader_callbacks = PackageDownloadCallbacks()
28base.set_download_callbacks(
29    libdnf5.repo.DownloadCallbacksUniquePtr(downloader_callbacks))
30
31# Download the packages.
32transaction.download()

DNF5 C++:

 1# include <libdnf5/repo/download_callbacks.hpp>
 2
 3class PackageDownloadCallbacks : public libdnf5::repo::DownloadCallbacks {
 4private:
 5    using TransferStatus = libdnf5::repo::DownloadCallbacks::TransferStatus;
 6
 7    /// List of descriptions of downloads.
 8    std::forward_list<std::string> download_descriptions;
 9
10    void * add_new_download([[maybe_unused]] void * user_data, const char * description, double total_to_download) {
11        std::cout << "Started downloading: " << description << ", size: " << total_to_download << std::endl;
12
13        // Store the message describing the new download and return a pointer to it.
14        return &download_descriptions.emplace_front(description);
15    }
16
17    int end(void * user_cb_data, TransferStatus status, const char * msg) {
18        // Check that user_cb_data is present in download_descriptions.
19        std::string * description{nullptr};
20        for (const auto & item : download_descriptions) {
21            if (&item == user_cb_data) {
22                // Get the description from the user_cb_data.
23                description = reinterpret_cast<std::string *>(user_cb_data);
24                break;
25            }
26        }
27        if (!description) {
28            return 0;
29        }
30
31        switch (status) {
32            case TransferStatus::SUCCESSFUL:
33                std::cout << "  Downloaded: " << *description << std::endl;
34                break;
35            case TransferStatus::ALREADYEXISTS:
36                std::cout << "  Already downloaded: " << *description << std::endl;
37                break;
38            case TransferStatus::ERROR:
39                std::cout << "  Error downloading: " << *description << ": " << msg << std::endl;
40                break;
41        }
42
43        return 0;
44    }
45};
46
47// Set the download callbacks.
48base.set_download_callbacks(std::make_unique<PackageDownloadCallbacks>());
49
50// Download the packages.
51transaction.download();

For the transaction callbacks, DNF4 class dnf.callback.TransactionProgress mainly uses its progress method to report almost all events, differentiating them with the action argument. In DNF5, the libdnf5::rpm::TransactionCallbacks class has multiple methods that can be overridden, differentiated by possible events that can happen during a transaction.

DNF4 Python:

 1class TransactionCallbacks(dnf.callback.TransactionProgress):
 2    def progress(self, package, action, ti_done, ti_total, ts_done, ts_total):
 3        if action == dnf.transaction.PKG_INSTALL and ti_done == ti_total:
 4            print(f"Installed: {package.name}-{package.evr}.{package.arch}")
 5        elif action == dnf.transaction.PKG_SCRIPTLET:
 6            print(f"Running scriptlet for: {package.name}-{package.evr}.{package.arch}")
 7
 8    def error(self, message):
 9        print(f"Transaction error: {message}")
10
11# Run the transaction.
12base.do_transaction(TransactionCallbacks())

DNF5 Python:

 1class TransactionCallbacks(libdnf5.rpm.TransactionCallbacks):
 2    def install_stop(self, item, amount, total):
 3        if item.get_action() == libdnf5.transaction.TransactionItemAction_INSTALL:
 4            print(f"Installed: {item.get_package().get_full_nevra()}")
 5
 6    def script_start(self, item, nevra, type):
 7        script = libdnf5.rpm.TransactionCallbacks.script_type_to_string(type)
 8        nevra_str = libdnf5.rpm.to_full_nevra_string(nevra)
 9        print(f"Running {script} scriptlet for: {nevra_str}")
10
11    def unpack_error(self, item):
12        print(f"Unpack error: {item.get_package().get_full_nevra()}")
13
14    def cpio_error(self, item):
15        print(f"Cpio error: {item.get_package().get_full_nevra()}")
16
17    def script_error(self, item, nevra, type, return_code):
18        script = libdnf5.rpm.TransactionCallbacks.script_type_to_string(type)
19        nevra_str = libdnf5.rpm.to_full_nevra_string(nevra)
20        print(f"Error in {script} scriptlet: {nevra_str} - return code {return_code}")
21
22
23# Set the transaction callbacks.
24transaction_callbacks = TransactionCallbacks()
25transaction.set_callbacks(
26    libdnf5.rpm.TransactionCallbacksUniquePtr(transaction_callbacks))
27
28# Run the transaction.
29transaction.run()

DNF5 C++:

 1# include <libdnf5/base/transaction_package.hpp>
 2# include <libdnf5/rpm/nevra.hpp>
 3# include <libdnf5/rpm/transaction_callbacks.hpp>
 4# include <libdnf5/transaction/transaction_item_action.hpp>
 5
 6class TransactionCallbacks : public libdnf5::rpm::TransactionCallbacks {
 7private:
 8    using tc = libdnf5::rpm::TransactionCallbacks;
 9
10    void install_stop(
11        const libdnf5::base::TransactionPackage & item,
12        [[maybe_unused]] uint64_t amount,
13        [[maybe_unused]] uint64_t total) {
14        if (item.get_action() == libdnf5::transaction::TransactionItemAction::INSTALL) {
15            std::cout << "Installed: " << item.get_package().get_full_nevra() << std::endl;
16        }
17    }
18
19    void script_start(
20        [[maybe_unused]] const libdnf5::base::TransactionPackage * item,
21        libdnf5::rpm::Nevra nevra,
22        tc::ScriptType type) {
23        std::cout << "Running " << tc::script_type_to_string(type)
24                  << "scriptlet for:" << libdnf5::rpm::to_full_nevra_string(nevra) << std::endl;
25    }
26
27    void unpack_error(const libdnf5::base::TransactionPackage & item) {
28        std::cout << "Unpack error: " << item.get_package().get_full_nevra() << std::endl;
29    }
30    void cpio_error(const libdnf5::base::TransactionPackage & item) {
31        std::cout << "Cpio error: " << item.get_package().get_full_nevra() << std::endl;
32    }
33    void script_error(
34        [[maybe_unused]] const libdnf5::base::TransactionPackage * item,
35        libdnf5::rpm::Nevra nevra,
36        tc::ScriptType type,
37        uint64_t return_code) {
38        std::cout << "Error in " << tc::script_type_to_string(type)
39                  << " scriptlet: " << libdnf5::rpm::to_full_nevra_string(nevra) << " - return code " << return_code
40                  << std::endl;
41    }
42};
43
44// Set the transaction callbacks.
45transaction.set_callbacks(std::make_unique<TransactionCallbacks>());
46
47// Download the packages.
48transaction.download();