diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 6b27dcca7..577253775 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,7 +1,13 @@ version: 2 +build: + os: ubuntu-22.04 + apt_packages: + - swig + tools: + python: "3.11" + python: - version: 3 install: - requirements: doc/requirements.txt # the 'setuptools' method calls doc/setup.py that configures files and runs doxygen diff --git a/doc/conf.py.in b/doc/conf.py.in index 1a661e95c..0e0b619ed 100644 --- a/doc/conf.py.in +++ b/doc/conf.py.in @@ -41,10 +41,31 @@ needs_sphinx = '4.1.2' extensions = [ 'breathe', 'sphinx.ext.autodoc', + # Add autoapi extension only if running in readthedocs workflow, when running with cmake + # it gets replace by empty value because @AUTOAPI_EXTENSION@ is not defined. + # We use autoapi in readthedocs because it can parse code directly (code generated by swig) + # to access doc strings. The benefit is that we don't need to do a full build of the library + # to create working libdnf5 python module which is required by autodoc to get doc string. + # We can't use autoapi in our regular builds because it is not currently packaged in fedora, + # but this is not a problem because regular builds have access to the full working python + # module and can use autodoc normally. + @AUTOAPI_EXTENSION@ ] breathe_projects = {'dnf5': '@CMAKE_CURRENT_BINARY_DIR@/xml/'} breathe_default_project = 'dnf5' +# The autoapi config is only used doc/setup.py in readthedocs workflow (not by cmake) +autoapi_type = 'python' +autoapi_dirs = ['@CMAKE_CURRENT_BINARY_DIR@/../bindings/python3/libdnf5', '@CMAKE_CURRENT_BINARY_DIR@/../bindings/python3/libdnf5_cli'] +autoapi_root = "api/python/autoapi" +autoapi_python_use_implicit_namespaces = True +# We don't want to automatically generate the full documentation. +# Instead we use directives, this allows greater control and both +# cpp and python docs can have the same look and layout. +autoapi_generate_api_docs = False +autoapi_add_toctree_entry = False +autoapi_add_objects_to_toctree = False + if sphinx.version_info[:3] > (4, 0, 0): tags.add('sphinx4') extensions += ['dbusdoc'] diff --git a/doc/requirements.txt b/doc/requirements.txt index cd6467ed8..32089dc94 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1 +1,2 @@ breathe +sphinx-autoapi diff --git a/doc/setup.py b/doc/setup.py index f84b0c41c..6747cab9c 100644 --- a/doc/setup.py +++ b/doc/setup.py @@ -25,9 +25,31 @@ def configure(src_path, dst_path, substitutions): open(dst_path, "w").write(data) +def generate_bindings_from_dir(in_dir, out_dir): + swig_options = ["-python", "-DSWIGWORDSIZE64", "-doxygen", "-relativeimport", "-outdir", out_dir, "-c++"] + swig_includes = ["-I" + os.path.join(DIR, "../include"), "-I" + os.path.join(DIR, "../common"), "-I/usr/include/python3.11"] + + for in_file in os.listdir(in_dir): + # exclude shared.i which is not a standalone interface file + # it is included in other files. + if in_file.endswith(".i") and in_file != "shared.i": + print("Generating bindings for: " + in_file) + subprocess.run(["/usr/bin/swig"] + swig_options + ["-interface", "_" + in_file[:-2]] + swig_includes + [os.path.join(in_dir, in_file)], cwd=DIR, check=True) + + # configure the .in files configure("Doxyfile.in", "Doxyfile", {"@CMAKE_SOURCE_DIR@": os.path.join(DIR, "..")}) -configure("conf.py.in", "conf.py", {"@CMAKE_CURRENT_BINARY_DIR@": DIR}) +configure("conf.py.in", "conf.py", {"@CMAKE_CURRENT_BINARY_DIR@": DIR, "@AUTOAPI_EXTENSION@": "\'autoapi.extension\',"}) + +# In place replace autodoc directives with autoapidoc directives. +# Our readthedocs workflow uses autoapidoc instead of autodoc because +# it is able to parse code generated by swig without including the +# module -> therefore there is no need for a full build in order to +# generate python docs. +for in_file in os.listdir(os.path.join(DIR + "/api/python")): + if in_file.endswith(".rst"): + file = os.path.join("api/python/", in_file) + configure(file, file, {"autoclass": "autoapiclass"}) # run doxygen manually @@ -35,5 +57,15 @@ def configure(src_path, dst_path, substitutions): subprocess.run(["doxygen"], cwd=DIR, check=True) +# run swig manually to generate Python bindings which are then used to generate Python API docs +print("Running SWIG...") + +# libdnf5 +# Generate bindings outside of doc dir, into their python bindings dir. This has to match with path provided to autoapi_dirs in conf.py.in +generate_bindings_from_dir(os.path.join(DIR + "/../bindings/libdnf5"), os.path.join(DIR + "/../bindings/python3/libdnf5")) + +# libdnf5-cli +generate_bindings_from_dir(os.path.join(DIR + "/../bindings/libdnf5_cli"), os.path.join(DIR + "/../bindings/python3/libdnf5_cli")) + # no setup() is called # this file only configures files for building docs in Read the Docs diff --git a/libdnf/solv/pool.hpp b/libdnf/solv/pool.hpp index 8facbd028..4d785955f 100644 --- a/libdnf/solv/pool.hpp +++ b/libdnf/solv/pool.hpp @@ -252,7 +252,7 @@ class CompsPool : public Pool { // Search solvables that correspond to the environment_ids for given key // Return first non-empty string template - std::string lookup_first_id_str(std::vector ids, Id key) { + std::string lookup_first_id_str(const std::vector & ids, Id key) { for (T id : ids) { auto value = lookup_str(id.id, key); if (value) { @@ -264,7 +264,7 @@ class CompsPool : public Pool { template - std::string get_translated_str(std::vector ids, Id key, const char * lang = nullptr) { + std::string get_translated_str(const std::vector & ids, Id key, const char * lang = nullptr) { // Go through all environment solvables and return first translation found. for (T id : ids) { Solvable * solvable = id2solvable(id.id);