Skip to content

Commit 7f93264

Browse files
committed
dts: add API doc generation
The tool used to generate DTS API docs is Sphinx, which is already in use in DPDK. The same configuration is used to preserve style with one DTS-specific configuration (so that the DPDK docs are unchanged) that modifies how the sidebar displays the content. There's other Sphinx configuration related to Python docstrings which doesn't affect DPDK doc build. All new configuration is in a conditional block, applied only when DTS API docs are built to not interfere with DPDK doc build. Sphinx generates the documentation from Python docstrings. The docstring format is the Google format [0] which requires the sphinx.ext.napoleon extension. The other extension, sphinx.ext.intersphinx, enables linking to objects in external documentations, such as the Python documentation. There is one requirement for building DTS docs - the same Python version as DTS or higher, because Sphinx's autodoc extension imports the code. The dependencies needed to import the code don't have to be satisfied, as the autodoc extension allows us to mock the imports. The missing packages are taken from the DTS pyproject.toml file. And finally, the DTS API docs can be accessed from the DPDK API doxygen page. [0] https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings Signed-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech> Acked-by: Thomas Monjalon <thomas@monjalon.net> Reviewed-by: Jeremy Spewock <jspewock@iol.unh.edu> Reviewed-by: Dean Marx <dmarx@iol.unh.edu> Reviewed-by: Luca Vizzarro <luca.vizzarro@arm.com>
1 parent 1e472b5 commit 7f93264

14 files changed

+234
-2
lines changed

MAINTAINERS

+1
Original file line numberDiff line numberDiff line change
@@ -1900,6 +1900,7 @@ M: Paul Szczepanek <paul.szczepanek@arm.com>
19001900
M: Patrick Robb <probb@iol.unh.edu>
19011901
T: git://dpdk.org/next/dpdk-next-dts
19021902
F: dts/
1903+
F: buildtools/check-dts-requirements.py
19031904
F: devtools/dts-check-format.sh
19041905
F: doc/guides/tools/dts.rst
19051906

buildtools/call-sphinx-build.py

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
# set the version in environment for sphinx to pick up
1717
os.environ['DPDK_VERSION'] = version
18+
if 'dts' in src:
19+
os.environ['DTS_DOC_BUILD'] = "y"
1820

1921
sphinx_cmd = [sphinx] + extra_args
2022

buildtools/check-dts-requirements.py

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env python3
2+
# SPDX-License-Identifier: BSD-3-Clause
3+
# Copyright(c) 2024 PANTHEON.tech s.r.o.
4+
#
5+
"""Utilities for DTS dependencies.
6+
7+
The module can be used as an executable script,
8+
which verifies that the running Python version meets the version requirement of DTS.
9+
The script exits with the standard exit codes in this mode (0 is success, 1 is failure).
10+
11+
The module also contains a function, get_missing_imports,
12+
which looks for runtime dependencies in the DTS pyproject.toml file
13+
and returns a list of module names used in an import statement (import packages) that are missing.
14+
This function is not used when the module is run as a script and is available to be imported.
15+
"""
16+
17+
import configparser
18+
import importlib.metadata
19+
import importlib.util
20+
import os.path
21+
import platform
22+
23+
from packaging.version import Version
24+
25+
_VERSION_COMPARISON_CHARS = '^<>='
26+
# The names of packages used in import statements may be different from distribution package names.
27+
# We get distribution package names from pyproject.toml.
28+
# _EXTRA_DEPS adds those import names which don't match their distribution package name.
29+
_EXTRA_DEPS = {
30+
'invoke': {'version': '>=1.3'},
31+
'paramiko': {'version': '>=2.4'},
32+
'PyYAML': {'version': '^6.0', 'import_package': 'yaml'}
33+
}
34+
_DPDK_ROOT = os.path.dirname(os.path.dirname(__file__))
35+
_DTS_DEP_FILE_PATH = os.path.join(_DPDK_ROOT, 'dts', 'pyproject.toml')
36+
37+
38+
def _get_dependencies(cfg_file_path):
39+
cfg = configparser.ConfigParser()
40+
with open(cfg_file_path) as f:
41+
dts_deps_file_str = f.read()
42+
dts_deps_file_str = dts_deps_file_str.replace("\n]", "]")
43+
cfg.read_string(dts_deps_file_str)
44+
45+
deps_section = cfg['tool.poetry.dependencies']
46+
return {dep: {'version': deps_section[dep].strip('"\'')} for dep in deps_section}
47+
48+
49+
def get_missing_imports():
50+
"""Get missing DTS import packages from third party libraries.
51+
52+
Scan the DTS pyproject.toml file for dependencies and find those that are not installed.
53+
The dependencies in pyproject.toml are listed by their distribution package names,
54+
but the function finds the associated import packages - those used in import statements.
55+
56+
The function is not used when the module is run as a script. It should be imported.
57+
58+
Returns:
59+
A list of missing import packages.
60+
"""
61+
missing_imports = []
62+
req_deps = _get_dependencies(_DTS_DEP_FILE_PATH)
63+
req_deps.pop('python')
64+
65+
for req_dep, dep_data in (req_deps | _EXTRA_DEPS).items():
66+
req_ver = dep_data['version']
67+
try:
68+
import_package = dep_data['import_package']
69+
except KeyError:
70+
import_package = req_dep
71+
import_package = import_package.lower().replace('-', '_')
72+
73+
try:
74+
req_ver = Version(req_ver.strip(_VERSION_COMPARISON_CHARS))
75+
found_dep_ver = Version(importlib.metadata.version(req_dep))
76+
if found_dep_ver < req_ver:
77+
print(
78+
f'The version "{found_dep_ver}" of package "{req_dep}" '
79+
f'is lower than required "{req_ver}".'
80+
)
81+
except importlib.metadata.PackageNotFoundError:
82+
print(f'Package "{req_dep}" not found.')
83+
missing_imports.append(import_package)
84+
85+
return missing_imports
86+
87+
88+
if __name__ == '__main__':
89+
python_version = _get_dependencies(_DTS_DEP_FILE_PATH).pop('python')
90+
if python_version:
91+
sys_ver = Version(platform.python_version())
92+
req_ver = Version(python_version['version'].strip(_VERSION_COMPARISON_CHARS))
93+
if sys_ver < req_ver:
94+
print(
95+
f'The available Python version "{sys_ver}" is lower than required "{req_ver}".'
96+
)
97+
exit(1)

buildtools/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ get_numa_count_cmd = py3 + files('get-numa-count.py')
2424
get_test_suites_cmd = py3 + files('get-test-suites.py')
2525
has_hugepages_cmd = py3 + files('has-hugepages.py')
2626
cmdline_gen_cmd = py3 + files('dpdk-cmdline-gen.py')
27+
check_dts_requirements = py3 + files('check-dts-requirements.py')
2728

2829
# install any build tools that end-users might want also
2930
install_data([

doc/api/doxy-api-index.md

+3
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,6 @@ The public API headers are grouped by topics:
246246
[experimental APIs](@ref rte_compat.h),
247247
[ABI versioning](@ref rte_function_versioning.h),
248248
[version](@ref rte_version.h)
249+
250+
- **tests**:
251+
[**DTS**](@dts_api_main_page)

doc/api/doxy-api.conf.in

+2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ SEARCHENGINE = YES
124124
SORT_MEMBER_DOCS = NO
125125
SOURCE_BROWSER = YES
126126

127+
ALIASES = "dts_api_main_page=@DTS_API_MAIN_PAGE@"
128+
127129
EXAMPLE_PATH = @TOPDIR@/examples
128130
EXAMPLE_PATTERNS = *.c
129131
EXAMPLE_RECURSIVE = YES

doc/api/dts/custom.css

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../guides/custom.css

doc/api/dts/meson.build

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
# Copyright(c) 2023 PANTHEON.tech s.r.o.
3+
4+
sphinx = find_program('sphinx-build', required: get_option('enable_docs'))
5+
if not sphinx.found()
6+
subdir_done()
7+
endif
8+
9+
python_ver_satisfied = run_command(check_dts_requirements, check: false).returncode()
10+
if python_ver_satisfied != 0
11+
subdir_done()
12+
endif
13+
14+
cdata.set('DTS_API_MAIN_PAGE', join_paths('..', 'dts', 'html', 'index.html'))
15+
16+
extra_sphinx_args = ['-E', '-c', join_paths(doc_source_dir, 'guides')]
17+
if get_option('werror')
18+
extra_sphinx_args += '-W'
19+
endif
20+
21+
htmldir = join_paths(get_option('datadir'), 'doc', 'dpdk', 'dts')
22+
dts_api_html = custom_target('dts_api_html',
23+
output: 'html',
24+
command: [sphinx_wrapper, sphinx, meson.project_version(),
25+
meson.current_source_dir(), meson.current_build_dir(), extra_sphinx_args],
26+
build_by_default: get_option('enable_docs'),
27+
install: get_option('enable_docs'),
28+
install_dir: htmldir)
29+
30+
doc_targets += dts_api_html
31+
doc_target_names += 'DTS_API_HTML'

doc/api/meson.build

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
doxygen = find_program('doxygen', required: get_option('enable_docs'))
55

66
if not doxygen.found()
7+
# process DTS API doc build even if DPDK API doc build can't be done
8+
cdata = configuration_data()
9+
subdir('dts')
710
subdir_done()
811
endif
912

@@ -100,3 +103,5 @@ mandb = find_program('mandb', required: false)
100103
if mandb.found() and get_option('enable_docs')
101104
meson.add_install_script(mandb)
102105
endif
106+
107+
subdir('dts')

doc/guides/conf.py

+46-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from os.path import basename
1111
from os.path import dirname
1212
from os.path import join as path_join
13-
from sys import argv, stderr
13+
from sys import argv, stderr, path
1414

1515
import configparser
1616

@@ -58,6 +58,51 @@
5858
("tools/devbind", "dpdk-devbind",
5959
"check device status and bind/unbind them from drivers", "", 8)]
6060

61+
# DTS API docs additional configuration
62+
if environ.get('DTS_DOC_BUILD'):
63+
extensions = ['sphinx.ext.napoleon', 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
64+
# Napoleon enables the Google format of Python doscstrings.
65+
napoleon_numpy_docstring = False
66+
napoleon_attr_annotations = True
67+
napoleon_preprocess_types = True
68+
69+
# Autodoc pulls documentation from code.
70+
autodoc_default_options = {
71+
'members': True,
72+
'member-order': 'bysource',
73+
'show-inheritance': True,
74+
}
75+
autodoc_class_signature = 'separated'
76+
autodoc_typehints = 'both'
77+
autodoc_typehints_format = 'short'
78+
autodoc_typehints_description_target = 'documented'
79+
80+
# Intersphinx allows linking to external projects, such as Python docs.
81+
intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
82+
83+
# DTS docstring options.
84+
add_module_names = False
85+
toc_object_entries = True
86+
toc_object_entries_show_parents = 'hide'
87+
# DTS Sidebar config.
88+
html_theme_options = {
89+
'collapse_navigation': False,
90+
'navigation_depth': -1, # unlimited depth
91+
}
92+
93+
# Add path to DTS sources so that Sphinx can find them.
94+
dpdk_root = dirname(dirname(dirname(__file__)))
95+
path.append(path_join(dpdk_root, 'dts'))
96+
97+
# Get missing DTS dependencies. Add path to buildtools to find the get_missing_imports function.
98+
path.append(path_join(dpdk_root, 'buildtools'))
99+
import importlib
100+
# Ignore missing imports from DTS dependencies, allowing us to build the docs without them.
101+
# There's almost no difference between docs built with and without dependencies.
102+
# The qualified names of imported objects are fully expanded with dependencies, such as:
103+
# fabric.Connection (without) vs. fabric.connection.Connection (with)
104+
autodoc_mock_imports = importlib.import_module('check-dts-requirements').get_missing_imports()
105+
61106

62107
# ####### :numref: fallback ########
63108
# The following hook functions add some simple handling for the :numref:

doc/guides/contributing/documentation.rst

+2
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ added to by the developer.
133133
Building the Documentation
134134
--------------------------
135135

136+
.. _doc_dependencies:
137+
136138
Dependencies
137139
~~~~~~~~~~~~
138140

doc/guides/contributing/patches.rst

+4
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,10 @@ The script usage is::
499499
For both of the above scripts, the -n option is used to specify a number of commits from HEAD,
500500
and the -r option allows the user specify a ``git log`` range.
501501

502+
Additionally, when contributing to the DTS tool, patches should also be checked using
503+
the ``dts-check-format.sh`` script in the ``devtools`` directory of the DPDK repo.
504+
To run the script, extra :ref:`Python dependencies <dts_deps>` are needed.
505+
502506
.. _contrib_check_compilation:
503507

504508
Checking Compilation

doc/guides/tools/dts.rst

+38-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ DTS uses Poetry as its Python dependency management.
5454
Python build/development and runtime environments are the same and DTS development environment,
5555
DTS runtime environment or just plain DTS environment are used interchangeably.
5656

57+
.. _dts_deps:
5758

5859
Setting up DTS environment
5960
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -294,8 +295,15 @@ When adding code to the DTS framework, pay attention to the rest of the code
294295
and try not to divert much from it.
295296
The :ref:`DTS developer tools <dts_dev_tools>` will issue warnings
296297
when some of the basics are not met.
298+
You should also build the :ref:`API documentation <building_api_docs>`
299+
to address any issues found during the build.
297300

298-
The code must be properly documented with docstrings.
301+
The API documentation, which is a helpful reference when developing, may be accessed
302+
in the code directly or generated with the :ref:`API docs build steps <building_api_docs>`.
303+
When adding new files or modifying the directory structure,
304+
the corresponding changes must be made to DTS API doc sources in ``doc/api/dts``.
305+
306+
Speaking of which, the code must be properly documented with docstrings.
299307
The style must conform to the `Google style
300308
<https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings>`_.
301309
See an example of the style `here
@@ -430,6 +438,35 @@ the DTS code check and format script.
430438
Refer to the script for usage: ``devtools/dts-check-format.sh -h``.
431439

432440

441+
.. _building_api_docs:
442+
443+
Building DTS API docs
444+
---------------------
445+
446+
The documentation is built using the standard DPDK build system.
447+
See :doc:`../linux_gsg/build_dpdk` for more details on compiling DPDK with meson.
448+
449+
The :ref:`doc build dependencies <doc_dependencies>` may be installed with Poetry:
450+
451+
.. code-block:: console
452+
453+
poetry install --no-root --only docs
454+
poetry install --no-root --with docs # an alternative that will also install DTS dependencies
455+
poetry shell
456+
457+
After executing the meson command, build the documentation with:
458+
459+
.. code-block:: console
460+
461+
ninja -C build doc
462+
463+
The output is generated in ``build/doc/api/dts/html``.
464+
465+
.. note::
466+
467+
Make sure to fix any Sphinx warnings when adding or updating docstrings.
468+
469+
433470
Configuration Schema
434471
--------------------
435472

doc/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# SPDX-License-Identifier: BSD-3-Clause
22
# Copyright(c) 2018 Luca Boccassi <bluca@debian.org>
33

4+
doc_source_dir = meson.current_source_dir()
45
doc_targets = []
56
doc_target_names = []
67
subdir('api')

0 commit comments

Comments
 (0)