Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable Python docs on readthedocs #246

Merged
merged 4 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
21 changes: 21 additions & 0 deletions doc/conf.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down
1 change: 1 addition & 0 deletions doc/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
breathe
sphinx-autoapi
34 changes: 33 additions & 1 deletion doc/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,47 @@ 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
print("Running doxygen...")
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
4 changes: 2 additions & 2 deletions libdnf/solv/pool.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename T>
std::string lookup_first_id_str(std::vector<T> ids, Id key) {
std::string lookup_first_id_str(const std::vector<T> & ids, Id key) {
for (T id : ids) {
auto value = lookup_str(id.id, key);
if (value) {
Expand All @@ -264,7 +264,7 @@ class CompsPool : public Pool {


template <typename T>
std::string get_translated_str(std::vector<T> ids, Id key, const char * lang = nullptr) {
std::string get_translated_str(const std::vector<T> & 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);
Expand Down