[RFC,v4,3/4] dts: add doc generation

Message ID 20230831100407.59865-4-juraj.linkes@pantheon.tech (mailing list archive)
State Superseded, archived
Delegated to: Thomas Monjalon
Headers
Series dts: add dts api docs |

Checks

Context Check Description
ci/checkpatch warning coding style issues

Commit Message

Juraj Linkeš Aug. 31, 2023, 10:04 a.m. UTC
The tool used to generate developer docs is sphinx, which is already
used in DPDK. The configuration is kept the same to preserve the style.

Sphinx generates the documentation from Python docstrings. The docstring
format most suitable for DTS seems to be the Google format [0] which
requires the sphinx.ext.napoleon extension.

There are two requirements for building DTS docs:
* The same Python version as DTS or higher, because Sphinx import the
  code.
* Also the same Python packages as DTS, for the same reason.

[0] https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings

Signed-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>
---
 buildtools/call-sphinx-build.py | 29 ++++++++++++-------
 doc/api/meson.build             |  1 +
 doc/guides/conf.py              | 32 +++++++++++++++++----
 doc/guides/meson.build          |  1 +
 doc/guides/tools/dts.rst        | 29 +++++++++++++++++++
 dts/doc/doc-index.rst           | 17 +++++++++++
 dts/doc/meson.build             | 50 +++++++++++++++++++++++++++++++++
 dts/meson.build                 | 16 +++++++++++
 meson.build                     |  1 +
 9 files changed, 161 insertions(+), 15 deletions(-)
 create mode 100644 dts/doc/doc-index.rst
 create mode 100644 dts/doc/meson.build
 create mode 100644 dts/meson.build
  

Comments

Juraj Linkeš Sept. 20, 2023, 7:08 a.m. UTC | #1
<snip>

> diff --git a/doc/guides/conf.py b/doc/guides/conf.py
> index 0f7ff5282d..737e5a5688 100644
> --- a/doc/guides/conf.py
> +++ b/doc/guides/conf.py
> @@ -7,10 +7,9 @@
>  from sphinx import __version__ as sphinx_version
>  from os import listdir
>  from os import environ
> -from os.path import basename
> -from os.path import dirname
> +from os.path import basename, dirname
>  from os.path import join as path_join
> -from sys import argv, stderr
> +from sys import argv, stderr, path
>
>  import configparser
>
> @@ -24,6 +23,29 @@
>            file=stderr)
>      pass
>
> +extensions = ['sphinx.ext.napoleon']
> +
> +# Python docstring options
> +autodoc_default_options = {
> +    'members': True,
> +    'member-order': 'bysource',
> +    'show-inheritance': True,
> +}
> +autodoc_typehints = 'both'
> +autodoc_typehints_format = 'short'
> +napoleon_numpy_docstring = False
> +napoleon_attr_annotations = True
> +napoleon_use_ivar = True
> +napoleon_use_rtype = False
> +add_module_names = False
> +toc_object_entries = False
> +
> +# Sidebar config
> +html_theme_options = {
> +    'collapse_navigation': False,
> +    'navigation_depth': -1,
> +}
> +

Thomas, Bruce,

I've added this configuration which modifies the sidebar a bit. This
affects the DPDK docs so I'd like to know whether this is permissible.
I think the sidebar works better this way even with DPDK docs, but
that may be a personal preference.

Let me know what you think.

>  stop_on_error = ('-W' in argv)
>
>  project = 'Data Plane Development Kit'
  
Yoan Picchi Oct. 26, 2023, 4:43 p.m. UTC | #2
On 8/31/23 11:04, Juraj Linkeš wrote:
> The tool used to generate developer docs is sphinx, which is already
> used in DPDK. The configuration is kept the same to preserve the style.
> 
> Sphinx generates the documentation from Python docstrings. The docstring
> format most suitable for DTS seems to be the Google format [0] which
> requires the sphinx.ext.napoleon extension.
> 
> There are two requirements for building DTS docs:
> * The same Python version as DTS or higher, because Sphinx import the
>    code.
> * Also the same Python packages as DTS, for the same reason.
> 
> [0] https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings
> 
> Signed-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>
> ---
>   buildtools/call-sphinx-build.py | 29 ++++++++++++-------
>   doc/api/meson.build             |  1 +
>   doc/guides/conf.py              | 32 +++++++++++++++++----
>   doc/guides/meson.build          |  1 +
>   doc/guides/tools/dts.rst        | 29 +++++++++++++++++++
>   dts/doc/doc-index.rst           | 17 +++++++++++
>   dts/doc/meson.build             | 50 +++++++++++++++++++++++++++++++++
>   dts/meson.build                 | 16 +++++++++++
>   meson.build                     |  1 +
>   9 files changed, 161 insertions(+), 15 deletions(-)
>   create mode 100644 dts/doc/doc-index.rst
>   create mode 100644 dts/doc/meson.build
>   create mode 100644 dts/meson.build
> 
> diff --git a/buildtools/call-sphinx-build.py b/buildtools/call-sphinx-build.py
> index 39a60d09fa..c2f3acfb1d 100755
> --- a/buildtools/call-sphinx-build.py
> +++ b/buildtools/call-sphinx-build.py
> @@ -3,37 +3,46 @@
>   # Copyright(c) 2019 Intel Corporation
>   #
>   
> +import argparse
>   import sys
>   import os
>   from os.path import join
>   from subprocess import run, PIPE, STDOUT
>   from packaging.version import Version
>   
> -# assign parameters to variables
> -(sphinx, version, src, dst, *extra_args) = sys.argv[1:]
> +parser = argparse.ArgumentParser()
> +parser.add_argument('sphinx')
> +parser.add_argument('version')
> +parser.add_argument('src')
> +parser.add_argument('dst')
> +parser.add_argument('--dts-root', default='.')
> +args, extra_args = parser.parse_known_args()
>   
>   # set the version in environment for sphinx to pick up
> -os.environ['DPDK_VERSION'] = version
> +os.environ['DPDK_VERSION'] = args.version
> +os.environ['DTS_ROOT'] = args.dts_root
>   
>   # for sphinx version >= 1.7 add parallelism using "-j auto"
> -ver = run([sphinx, '--version'], stdout=PIPE,
> +ver = run([args.sphinx, '--version'], stdout=PIPE,
>             stderr=STDOUT).stdout.decode().split()[-1]
> -sphinx_cmd = [sphinx] + extra_args
> +sphinx_cmd = [args.sphinx] + extra_args
>   if Version(ver) >= Version('1.7'):
>       sphinx_cmd += ['-j', 'auto']
>   
>   # find all the files sphinx will process so we can write them as dependencies
>   srcfiles = []
> -for root, dirs, files in os.walk(src):
> +for root, dirs, files in os.walk(args.src):
>       srcfiles.extend([join(root, f) for f in files])
>   
>   # run sphinx, putting the html output in a "html" directory
> -with open(join(dst, 'sphinx_html.out'), 'w') as out:
> -    process = run(sphinx_cmd + ['-b', 'html', src, join(dst, 'html')],
> -                  stdout=out)
> +with open(join(args.dst, 'sphinx_html.out'), 'w') as out:
> +    process = run(
> +        sphinx_cmd + ['-b', 'html', args.src, join(args.dst, 'html')],
> +        stdout=out
> +    )
>   
>   # create a gcc format .d file giving all the dependencies of this doc build
> -with open(join(dst, '.html.d'), 'w') as d:
> +with open(join(args.dst, '.html.d'), 'w') as d:
>       d.write('html: ' + ' '.join(srcfiles) + '\n')
>   
>   sys.exit(process.returncode)
> diff --git a/doc/api/meson.build b/doc/api/meson.build
> index 2876a78a7e..1f0c725a94 100644
> --- a/doc/api/meson.build
> +++ b/doc/api/meson.build
> @@ -1,6 +1,7 @@
>   # SPDX-License-Identifier: BSD-3-Clause
>   # Copyright(c) 2018 Luca Boccassi <bluca@debian.org>
>   
> +doc_api_build_dir = meson.current_build_dir()
>   doxygen = find_program('doxygen', required: get_option('enable_docs'))
>   
>   if not doxygen.found()
> diff --git a/doc/guides/conf.py b/doc/guides/conf.py
> index 0f7ff5282d..737e5a5688 100644
> --- a/doc/guides/conf.py
> +++ b/doc/guides/conf.py
> @@ -7,10 +7,9 @@
>   from sphinx import __version__ as sphinx_version
>   from os import listdir
>   from os import environ
> -from os.path import basename
> -from os.path import dirname
> +from os.path import basename, dirname
>   from os.path import join as path_join
> -from sys import argv, stderr
> +from sys import argv, stderr, path
>   
>   import configparser
>   
> @@ -24,6 +23,29 @@
>             file=stderr)
>       pass
>   
> +extensions = ['sphinx.ext.napoleon']
> +
> +# Python docstring options
> +autodoc_default_options = {
> +    'members': True,
> +    'member-order': 'bysource',
> +    'show-inheritance': True,
> +}
> +autodoc_typehints = 'both'
> +autodoc_typehints_format = 'short'
> +napoleon_numpy_docstring = False
> +napoleon_attr_annotations = True
> +napoleon_use_ivar = True
> +napoleon_use_rtype = False
> +add_module_names = False
> +toc_object_entries = False
> +
> +# Sidebar config
> +html_theme_options = {
> +    'collapse_navigation': False,
> +    'navigation_depth': -1,
> +}
> +
>   stop_on_error = ('-W' in argv)
>   
>   project = 'Data Plane Development Kit'
> @@ -35,8 +57,8 @@
>   html_show_copyright = False
>   highlight_language = 'none'
>   
> -release = environ.setdefault('DPDK_VERSION', "None")
> -version = release
> +path.append(environ.get('DTS_ROOT'))
> +version = environ.setdefault('DPDK_VERSION', "None")
>   
>   master_doc = 'index'
>   
> diff --git a/doc/guides/meson.build b/doc/guides/meson.build
> index 51f81da2e3..8933d75f6b 100644
> --- a/doc/guides/meson.build
> +++ b/doc/guides/meson.build
> @@ -1,6 +1,7 @@
>   # SPDX-License-Identifier: BSD-3-Clause
>   # Copyright(c) 2018 Intel Corporation
>   
> +doc_guides_source_dir = meson.current_source_dir()
>   sphinx = find_program('sphinx-build', required: get_option('enable_docs'))
>   
>   if not sphinx.found()
> diff --git a/doc/guides/tools/dts.rst b/doc/guides/tools/dts.rst
> index 32c18ee472..98923b1467 100644
> --- a/doc/guides/tools/dts.rst
> +++ b/doc/guides/tools/dts.rst
> @@ -335,3 +335,32 @@ There are three tools used in DTS to help with code checking, style and formatti
>   These three tools are all used in ``devtools/dts-check-format.sh``,
>   the DTS code check and format script.
>   Refer to the script for usage: ``devtools/dts-check-format.sh -h``.
> +
> +
> +Building DTS API docs
> +---------------------
> +
> +To build DTS API docs, install the dependencies with Poetry, then enter its shell:
> +
> +   .. code-block:: console

I believe the code-block line should not be indented. As it is, it did 
not generate properly when I built the doc. Same for the other code 
block below.

> +
> +   poetry install --with docs
> +   poetry shell
> +
> +
> +Build commands
> +~~~~~~~~~~~~~~
> +
> +The documentation is built using the standard DPDK build system.
> +
> +After entering Poetry's shell, build the documentation with:
> +
> +   .. code-block:: console
> +
> +   ninja -C build dts/doc
> +
> +The output is generated in ``build/doc/api/dts/html``.
> +
> +.. Note::
> +
> +   Make sure to fix any Sphinx warnings when adding or updating docstrings.
> diff --git a/dts/doc/doc-index.rst b/dts/doc/doc-index.rst
> new file mode 100644
> index 0000000000..f5dcd553f2
> --- /dev/null
> +++ b/dts/doc/doc-index.rst
> @@ -0,0 +1,17 @@
> +.. DPDK Test Suite documentation.
> +
> +Welcome to DPDK Test Suite's documentation!
> +===========================================
> +
> +.. toctree::
> +   :titlesonly:
> +   :caption: Contents:
> +
> +   framework
> +
> +Indices and tables
> +==================
> +
> +* :ref:`genindex`
> +* :ref:`modindex`
> +* :ref:`search`
> diff --git a/dts/doc/meson.build b/dts/doc/meson.build
> new file mode 100644
> index 0000000000..8e70eabc51
> --- /dev/null
> +++ b/dts/doc/meson.build
> @@ -0,0 +1,50 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2023 PANTHEON.tech s.r.o.
> +
> +sphinx = find_program('sphinx-build')
> +sphinx_apidoc = find_program('sphinx-apidoc')
> +
> +if not sphinx.found() or not sphinx_apidoc.found()
> +    subdir_done()
> +endif
> +
> +dts_api_framework_dir = join_paths(dts_dir, 'framework')
> +dts_api_build_dir = join_paths(doc_api_build_dir, 'dts')
> +dts_api_src = custom_target('dts_api_src',
> +        output: 'modules.rst',
> +        command: [sphinx_apidoc, '--append-syspath', '--force',
> +            '--module-first', '--separate', '-V', meson.project_version(),
> +            '-o', dts_api_build_dir, '--no-toc', '--implicit-namespaces',
> +            dts_api_framework_dir],
> +        build_by_default: false)
> +doc_targets += dts_api_src
> +doc_target_names += 'DTS_API_sphinx_sources'
> +
> +cp = find_program('cp')
> +cp_index = custom_target('cp_index',
> +        input: 'doc-index.rst',
> +        output: 'index.rst',
> +        depends: dts_api_src,
> +        command: [cp, '@INPUT@', join_paths(dts_api_build_dir, 'index.rst')],
> +        build_by_default: false)
> +doc_targets += cp_index
> +doc_target_names += 'DTS_API_sphinx_index'
> +
> +extra_sphinx_args = ['-a', '-c', doc_guides_source_dir]
> +if get_option('werror')
> +    extra_sphinx_args += '-W'
> +endif
> +
> +htmldir = join_paths(get_option('datadir'), 'doc', 'dpdk')
> +dts_api_html = custom_target('dts_api_html',
> +        output: 'html',
> +        depends: cp_index,
> +        command: ['DTS_ROOT=@0@'.format(dts_dir),
> +            sphinx_wrapper, sphinx, meson.project_version(),
> +            dts_api_build_dir, dts_api_build_dir,
> +            '--dts-root', dts_dir, extra_sphinx_args],
> +        build_by_default: false,
> +        install: false,
> +        install_dir: htmldir)

I don't entirely understand what this command do. I suspect it's the one 
that create and populate the html directory in build/doc/api/dts/
The main thing that confuse me is this htmldir. Mine seemed to point to 
share/doc/dpdk. Such a directory doesn't seems to be built (or I could 
not find where). That might be intended given install is set to false. 
But in that case, why bother setting it?
The thing that worries me more is that dpdk/doc/guide/meson.build do 
build some html and have the same htmldir. If we were to enable the 
install in both, wouldn't the html overwrite each other (in particular 
index.html)?
If my understanding is correct, I believe htmldir should either be 
removed, or set to a different value (<datadir>/doc/dpdk/dts ?)

> +doc_targets += dts_api_html
> +doc_target_names += 'DTS_API_HTML'
> diff --git a/dts/meson.build b/dts/meson.build
> new file mode 100644
> index 0000000000..17bda07636
> --- /dev/null
> +++ b/dts/meson.build
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2023 PANTHEON.tech s.r.o.
> +
> +doc_targets = []
> +doc_target_names = []
> +dts_dir = meson.current_source_dir()
> +
> +subdir('doc')
> +
> +if doc_targets.length() == 0
> +    message = 'No docs targets found'
> +else
> +    message = 'Built docs:'
> +endif
> +run_target('dts/doc', command: [echo, message, doc_target_names],
> +    depends: doc_targets)
> diff --git a/meson.build b/meson.build
> index 39cb73846d..4d34dc531c 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -85,6 +85,7 @@ subdir('app')
>   
>   # build docs
>   subdir('doc')
> +subdir('dts')
>   
>   # build any examples explicitly requested - useful for developers - and
>   # install any example code into the appropriate install path

When building from scratch, I had several warning/errors.

$ meson build
[usual meson output block...]
WARNING: Target "dts/doc" has a path separator in its name.
This is not supported, it can cause unexpected failures and will become
a hard error in the future.
Configuring rte_build_config.h using configuration
Message:
=================
Applications Enabled
=================
[...]

If you change the target name, remember to change it in the doc too.



$ ninja -C build dts/doc
[create all the rst...]
[3/4] Generating dts_api_html with a custom command.
dpdk/dts/framework/remote_session/interactive_shell.py:docstring of 
framework.remote_session.interactive_shell.InteractiveShell:25: WARNING: 
Definition list ends without a blank line; unexpected unindent.

dpdk/dts/framework/remote_session/interactive_shell.py:docstring of 
framework.remote_session.interactive_shell.InteractiveShell:27: ERROR: 
Unexpected indentation.

dpdk/dts/framework/testbed_model/common.py:docstring of 
framework.testbed_model.common.MesonArgs:3: ERROR: Unexpected indentation.

dpdk/dts/framework/testbed_model/common.py:docstring of 
framework.testbed_model.common.MesonArgs:4: WARNING: Block quote ends 
without a blank line; unexpected unindent.

dpdk/dts/framework/testbed_model/linux_session.py:docstring of 
framework.testbed_model.linux_session.LshwOutput:10: ERROR: Unexpected 
indentation.

dpdk/dts/framework/testbed_model/linux_session.py:docstring of 
framework.testbed_model.linux_session.LshwOutput:13: WARNING: Block 
quote ends without a blank line; unexpected unindent.

dpdk/dts/framework/testbed_model/sut_node.py:docstring of 
framework.testbed_model.sut_node.SutNode.create_eal_parameters:3: ERROR: 
Unexpected indentation.

dpdk/dts/framework/testbed_model/sut_node.py:docstring of 
framework.testbed_model.sut_node.SutNode.create_eal_parameters:6: 
WARNING: Block quote ends without a blank line; unexpected unindent.

dpdk/dts/framework/testbed_model/sut_node.py:docstring of 
framework.testbed_model.sut_node.SutNode.create_eal_parameters:18: 
ERROR: Unexpected indentation.

dpdk/dts/framework/testbed_model/sut_node.py:docstring of 
framework.testbed_model.sut_node.SutNode.create_eal_parameters:20: 
WARNING: Block quote ends without a blank line; unexpected unindent.

dpdk/dts/framework/test_suite.py:docstring of 
framework.test_suite.TestSuite:1: ERROR: Unknown target name: "test".

dpdk/dts/framework/test_suite.py:docstring of 
framework.test_suite.TestSuite:1: ERROR: Unknown target name: "test_perf".

If I then try to rerun ninja, those errors don't appear, so it seems to 
happen mostly on fresh build.
  
Juraj Linkeš Oct. 27, 2023, 9:52 a.m. UTC | #3
Thanks for the comments, Yoan.

> > diff --git a/doc/guides/tools/dts.rst b/doc/guides/tools/dts.rst
> > index 32c18ee472..98923b1467 100644
> > --- a/doc/guides/tools/dts.rst
> > +++ b/doc/guides/tools/dts.rst
> > @@ -335,3 +335,32 @@ There are three tools used in DTS to help with code checking, style and formatti
> >   These three tools are all used in ``devtools/dts-check-format.sh``,
> >   the DTS code check and format script.
> >   Refer to the script for usage: ``devtools/dts-check-format.sh -h``.
> > +
> > +
> > +Building DTS API docs
> > +---------------------
> > +
> > +To build DTS API docs, install the dependencies with Poetry, then enter its shell:
> > +
> > +   .. code-block:: console
>
> I believe the code-block line should not be indented. As it is, it did
> not generate properly when I built the doc. Same for the other code
> block below.
>

Good catch, thanks.

> > diff --git a/dts/doc/meson.build b/dts/doc/meson.build
> > new file mode 100644
> > index 0000000000..8e70eabc51
> > --- /dev/null
> > +++ b/dts/doc/meson.build
> > @@ -0,0 +1,50 @@
> > +# SPDX-License-Identifier: BSD-3-Clause
> > +# Copyright(c) 2023 PANTHEON.tech s.r.o.
> > +
> > +sphinx = find_program('sphinx-build')
> > +sphinx_apidoc = find_program('sphinx-apidoc')
> > +
> > +if not sphinx.found() or not sphinx_apidoc.found()
> > +    subdir_done()
> > +endif
> > +
> > +dts_api_framework_dir = join_paths(dts_dir, 'framework')
> > +dts_api_build_dir = join_paths(doc_api_build_dir, 'dts')
> > +dts_api_src = custom_target('dts_api_src',
> > +        output: 'modules.rst',
> > +        command: [sphinx_apidoc, '--append-syspath', '--force',
> > +            '--module-first', '--separate', '-V', meson.project_version(),
> > +            '-o', dts_api_build_dir, '--no-toc', '--implicit-namespaces',
> > +            dts_api_framework_dir],
> > +        build_by_default: false)
> > +doc_targets += dts_api_src
> > +doc_target_names += 'DTS_API_sphinx_sources'
> > +
> > +cp = find_program('cp')
> > +cp_index = custom_target('cp_index',
> > +        input: 'doc-index.rst',
> > +        output: 'index.rst',
> > +        depends: dts_api_src,
> > +        command: [cp, '@INPUT@', join_paths(dts_api_build_dir, 'index.rst')],
> > +        build_by_default: false)
> > +doc_targets += cp_index
> > +doc_target_names += 'DTS_API_sphinx_index'
> > +
> > +extra_sphinx_args = ['-a', '-c', doc_guides_source_dir]
> > +if get_option('werror')
> > +    extra_sphinx_args += '-W'
> > +endif
> > +
> > +htmldir = join_paths(get_option('datadir'), 'doc', 'dpdk')
> > +dts_api_html = custom_target('dts_api_html',
> > +        output: 'html',
> > +        depends: cp_index,
> > +        command: ['DTS_ROOT=@0@'.format(dts_dir),
> > +            sphinx_wrapper, sphinx, meson.project_version(),
> > +            dts_api_build_dir, dts_api_build_dir,
> > +            '--dts-root', dts_dir, extra_sphinx_args],
> > +        build_by_default: false,
> > +        install: false,
> > +        install_dir: htmldir)
>
> I don't entirely understand what this command do. I suspect it's the one
> that create and populate the html directory in build/doc/api/dts/

Yes, this one generates the sphinx html docs from the .rst files
generated in the first step.

> The main thing that confuse me is this htmldir. Mine seemed to point to
> share/doc/dpdk. Such a directory doesn't seems to be built (or I could
> not find where). That might be intended given install is set to false.
> But in that case, why bother setting it?
> The thing that worries me more is that dpdk/doc/guide/meson.build do
> build some html and have the same htmldir. If we were to enable the
> install in both, wouldn't the html overwrite each other (in particular
> index.html)?
> If my understanding is correct, I believe htmldir should either be
> removed, or set to a different value (<datadir>/doc/dpdk/dts ?)

These install related configs are basically a placeholder (install:
false) and a copy-paste from the doxygen API generation because meson
required these to be filles (IIRC).
I was hoping Bruce would give me some guidance in this.

Bruce, how should we install the DTS API docs? I'm not really sure how
exactly the meson install procedure works, what's going to be copied
and so on. I don't really know what to do with this.

>
> > +doc_targets += dts_api_html
> > +doc_target_names += 'DTS_API_HTML'
> > diff --git a/dts/meson.build b/dts/meson.build
> > new file mode 100644
> > index 0000000000..17bda07636
> > --- /dev/null
> > +++ b/dts/meson.build
> > @@ -0,0 +1,16 @@
> > +# SPDX-License-Identifier: BSD-3-Clause
> > +# Copyright(c) 2023 PANTHEON.tech s.r.o.
> > +
> > +doc_targets = []
> > +doc_target_names = []
> > +dts_dir = meson.current_source_dir()
> > +
> > +subdir('doc')
> > +
> > +if doc_targets.length() == 0
> > +    message = 'No docs targets found'
> > +else
> > +    message = 'Built docs:'
> > +endif
> > +run_target('dts/doc', command: [echo, message, doc_target_names],
> > +    depends: doc_targets)
> > diff --git a/meson.build b/meson.build
> > index 39cb73846d..4d34dc531c 100644
> > --- a/meson.build
> > +++ b/meson.build
> > @@ -85,6 +85,7 @@ subdir('app')
> >
> >   # build docs
> >   subdir('doc')
> > +subdir('dts')
> >
> >   # build any examples explicitly requested - useful for developers - and
> >   # install any example code into the appropriate install path
>
> When building from scratch, I had several warning/errors.
>
> $ meson build
> [usual meson output block...]
> WARNING: Target "dts/doc" has a path separator in its name.
> This is not supported, it can cause unexpected failures and will become
> a hard error in the future.

This is likely due to a newer meson version that I'm using, which is
0.53.2 because that's used in CI. We can rename it to satisfy newer
versions.

> Configuring rte_build_config.h using configuration
> Message:
> =================
> Applications Enabled
> =================
> [...]
>
> If you change the target name, remember to change it in the doc too.
>
>
>
> $ ninja -C build dts/doc
> [create all the rst...]
> [3/4] Generating dts_api_html with a custom command.
> dpdk/dts/framework/remote_session/interactive_shell.py:docstring of
> framework.remote_session.interactive_shell.InteractiveShell:25: WARNING:
> Definition list ends without a blank line; unexpected unindent.
>
> dpdk/dts/framework/remote_session/interactive_shell.py:docstring of
> framework.remote_session.interactive_shell.InteractiveShell:27: ERROR:
> Unexpected indentation.
>
> dpdk/dts/framework/testbed_model/common.py:docstring of
> framework.testbed_model.common.MesonArgs:3: ERROR: Unexpected indentation.
>
> dpdk/dts/framework/testbed_model/common.py:docstring of
> framework.testbed_model.common.MesonArgs:4: WARNING: Block quote ends
> without a blank line; unexpected unindent.
>
> dpdk/dts/framework/testbed_model/linux_session.py:docstring of
> framework.testbed_model.linux_session.LshwOutput:10: ERROR: Unexpected
> indentation.
>
> dpdk/dts/framework/testbed_model/linux_session.py:docstring of
> framework.testbed_model.linux_session.LshwOutput:13: WARNING: Block
> quote ends without a blank line; unexpected unindent.
>
> dpdk/dts/framework/testbed_model/sut_node.py:docstring of
> framework.testbed_model.sut_node.SutNode.create_eal_parameters:3: ERROR:
> Unexpected indentation.
>
> dpdk/dts/framework/testbed_model/sut_node.py:docstring of
> framework.testbed_model.sut_node.SutNode.create_eal_parameters:6:
> WARNING: Block quote ends without a blank line; unexpected unindent.
>
> dpdk/dts/framework/testbed_model/sut_node.py:docstring of
> framework.testbed_model.sut_node.SutNode.create_eal_parameters:18:
> ERROR: Unexpected indentation.
>
> dpdk/dts/framework/testbed_model/sut_node.py:docstring of
> framework.testbed_model.sut_node.SutNode.create_eal_parameters:20:
> WARNING: Block quote ends without a blank line; unexpected unindent.
>
> dpdk/dts/framework/test_suite.py:docstring of
> framework.test_suite.TestSuite:1: ERROR: Unknown target name: "test".
>
> dpdk/dts/framework/test_suite.py:docstring of
> framework.test_suite.TestSuite:1: ERROR: Unknown target name: "test_perf".
>

These errors are here because the docstrings are either incomplete or
not yet reformatted. These will be addressed in a new version that's
coming soon.

> If I then try to rerun ninja, those errors don't appear, so it seems to
> happen mostly on fresh build.
>

Yes, that is expected.

This made me look into how we're running sphinx-build. We're using:
  -a                write all files (default: only write new and changed files)

in the hope of forcing a full rebuild, but that doesn't seem to be
working properly, if at all. What I actually want sphinx to do is to
update the .html files after a rebuild and -a doesn't help with that.

I've tried the -E option instead and that seems to be working - it
updates the modified .html files so I'll replace -a with -E in the new
version.
  

Patch

diff --git a/buildtools/call-sphinx-build.py b/buildtools/call-sphinx-build.py
index 39a60d09fa..c2f3acfb1d 100755
--- a/buildtools/call-sphinx-build.py
+++ b/buildtools/call-sphinx-build.py
@@ -3,37 +3,46 @@ 
 # Copyright(c) 2019 Intel Corporation
 #
 
+import argparse
 import sys
 import os
 from os.path import join
 from subprocess import run, PIPE, STDOUT
 from packaging.version import Version
 
-# assign parameters to variables
-(sphinx, version, src, dst, *extra_args) = sys.argv[1:]
+parser = argparse.ArgumentParser()
+parser.add_argument('sphinx')
+parser.add_argument('version')
+parser.add_argument('src')
+parser.add_argument('dst')
+parser.add_argument('--dts-root', default='.')
+args, extra_args = parser.parse_known_args()
 
 # set the version in environment for sphinx to pick up
-os.environ['DPDK_VERSION'] = version
+os.environ['DPDK_VERSION'] = args.version
+os.environ['DTS_ROOT'] = args.dts_root
 
 # for sphinx version >= 1.7 add parallelism using "-j auto"
-ver = run([sphinx, '--version'], stdout=PIPE,
+ver = run([args.sphinx, '--version'], stdout=PIPE,
           stderr=STDOUT).stdout.decode().split()[-1]
-sphinx_cmd = [sphinx] + extra_args
+sphinx_cmd = [args.sphinx] + extra_args
 if Version(ver) >= Version('1.7'):
     sphinx_cmd += ['-j', 'auto']
 
 # find all the files sphinx will process so we can write them as dependencies
 srcfiles = []
-for root, dirs, files in os.walk(src):
+for root, dirs, files in os.walk(args.src):
     srcfiles.extend([join(root, f) for f in files])
 
 # run sphinx, putting the html output in a "html" directory
-with open(join(dst, 'sphinx_html.out'), 'w') as out:
-    process = run(sphinx_cmd + ['-b', 'html', src, join(dst, 'html')],
-                  stdout=out)
+with open(join(args.dst, 'sphinx_html.out'), 'w') as out:
+    process = run(
+        sphinx_cmd + ['-b', 'html', args.src, join(args.dst, 'html')],
+        stdout=out
+    )
 
 # create a gcc format .d file giving all the dependencies of this doc build
-with open(join(dst, '.html.d'), 'w') as d:
+with open(join(args.dst, '.html.d'), 'w') as d:
     d.write('html: ' + ' '.join(srcfiles) + '\n')
 
 sys.exit(process.returncode)
diff --git a/doc/api/meson.build b/doc/api/meson.build
index 2876a78a7e..1f0c725a94 100644
--- a/doc/api/meson.build
+++ b/doc/api/meson.build
@@ -1,6 +1,7 @@ 
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2018 Luca Boccassi <bluca@debian.org>
 
+doc_api_build_dir = meson.current_build_dir()
 doxygen = find_program('doxygen', required: get_option('enable_docs'))
 
 if not doxygen.found()
diff --git a/doc/guides/conf.py b/doc/guides/conf.py
index 0f7ff5282d..737e5a5688 100644
--- a/doc/guides/conf.py
+++ b/doc/guides/conf.py
@@ -7,10 +7,9 @@ 
 from sphinx import __version__ as sphinx_version
 from os import listdir
 from os import environ
-from os.path import basename
-from os.path import dirname
+from os.path import basename, dirname
 from os.path import join as path_join
-from sys import argv, stderr
+from sys import argv, stderr, path
 
 import configparser
 
@@ -24,6 +23,29 @@ 
           file=stderr)
     pass
 
+extensions = ['sphinx.ext.napoleon']
+
+# Python docstring options
+autodoc_default_options = {
+    'members': True,
+    'member-order': 'bysource',
+    'show-inheritance': True,
+}
+autodoc_typehints = 'both'
+autodoc_typehints_format = 'short'
+napoleon_numpy_docstring = False
+napoleon_attr_annotations = True
+napoleon_use_ivar = True
+napoleon_use_rtype = False
+add_module_names = False
+toc_object_entries = False
+
+# Sidebar config
+html_theme_options = {
+    'collapse_navigation': False,
+    'navigation_depth': -1,
+}
+
 stop_on_error = ('-W' in argv)
 
 project = 'Data Plane Development Kit'
@@ -35,8 +57,8 @@ 
 html_show_copyright = False
 highlight_language = 'none'
 
-release = environ.setdefault('DPDK_VERSION', "None")
-version = release
+path.append(environ.get('DTS_ROOT'))
+version = environ.setdefault('DPDK_VERSION', "None")
 
 master_doc = 'index'
 
diff --git a/doc/guides/meson.build b/doc/guides/meson.build
index 51f81da2e3..8933d75f6b 100644
--- a/doc/guides/meson.build
+++ b/doc/guides/meson.build
@@ -1,6 +1,7 @@ 
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2018 Intel Corporation
 
+doc_guides_source_dir = meson.current_source_dir()
 sphinx = find_program('sphinx-build', required: get_option('enable_docs'))
 
 if not sphinx.found()
diff --git a/doc/guides/tools/dts.rst b/doc/guides/tools/dts.rst
index 32c18ee472..98923b1467 100644
--- a/doc/guides/tools/dts.rst
+++ b/doc/guides/tools/dts.rst
@@ -335,3 +335,32 @@  There are three tools used in DTS to help with code checking, style and formatti
 These three tools are all used in ``devtools/dts-check-format.sh``,
 the DTS code check and format script.
 Refer to the script for usage: ``devtools/dts-check-format.sh -h``.
+
+
+Building DTS API docs
+---------------------
+
+To build DTS API docs, install the dependencies with Poetry, then enter its shell:
+
+   .. code-block:: console
+
+   poetry install --with docs
+   poetry shell
+
+
+Build commands
+~~~~~~~~~~~~~~
+
+The documentation is built using the standard DPDK build system.
+
+After entering Poetry's shell, build the documentation with:
+
+   .. code-block:: console
+
+   ninja -C build dts/doc
+
+The output is generated in ``build/doc/api/dts/html``.
+
+.. Note::
+
+   Make sure to fix any Sphinx warnings when adding or updating docstrings.
diff --git a/dts/doc/doc-index.rst b/dts/doc/doc-index.rst
new file mode 100644
index 0000000000..f5dcd553f2
--- /dev/null
+++ b/dts/doc/doc-index.rst
@@ -0,0 +1,17 @@ 
+.. DPDK Test Suite documentation.
+
+Welcome to DPDK Test Suite's documentation!
+===========================================
+
+.. toctree::
+   :titlesonly:
+   :caption: Contents:
+
+   framework
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/dts/doc/meson.build b/dts/doc/meson.build
new file mode 100644
index 0000000000..8e70eabc51
--- /dev/null
+++ b/dts/doc/meson.build
@@ -0,0 +1,50 @@ 
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+
+sphinx = find_program('sphinx-build')
+sphinx_apidoc = find_program('sphinx-apidoc')
+
+if not sphinx.found() or not sphinx_apidoc.found()
+    subdir_done()
+endif
+
+dts_api_framework_dir = join_paths(dts_dir, 'framework')
+dts_api_build_dir = join_paths(doc_api_build_dir, 'dts')
+dts_api_src = custom_target('dts_api_src',
+        output: 'modules.rst',
+        command: [sphinx_apidoc, '--append-syspath', '--force',
+            '--module-first', '--separate', '-V', meson.project_version(),
+            '-o', dts_api_build_dir, '--no-toc', '--implicit-namespaces',
+            dts_api_framework_dir],
+        build_by_default: false)
+doc_targets += dts_api_src
+doc_target_names += 'DTS_API_sphinx_sources'
+
+cp = find_program('cp')
+cp_index = custom_target('cp_index',
+        input: 'doc-index.rst',
+        output: 'index.rst',
+        depends: dts_api_src,
+        command: [cp, '@INPUT@', join_paths(dts_api_build_dir, 'index.rst')],
+        build_by_default: false)
+doc_targets += cp_index
+doc_target_names += 'DTS_API_sphinx_index'
+
+extra_sphinx_args = ['-a', '-c', doc_guides_source_dir]
+if get_option('werror')
+    extra_sphinx_args += '-W'
+endif
+
+htmldir = join_paths(get_option('datadir'), 'doc', 'dpdk')
+dts_api_html = custom_target('dts_api_html',
+        output: 'html',
+        depends: cp_index,
+        command: ['DTS_ROOT=@0@'.format(dts_dir),
+            sphinx_wrapper, sphinx, meson.project_version(),
+            dts_api_build_dir, dts_api_build_dir,
+            '--dts-root', dts_dir, extra_sphinx_args],
+        build_by_default: false,
+        install: false,
+        install_dir: htmldir)
+doc_targets += dts_api_html
+doc_target_names += 'DTS_API_HTML'
diff --git a/dts/meson.build b/dts/meson.build
new file mode 100644
index 0000000000..17bda07636
--- /dev/null
+++ b/dts/meson.build
@@ -0,0 +1,16 @@ 
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+
+doc_targets = []
+doc_target_names = []
+dts_dir = meson.current_source_dir()
+
+subdir('doc')
+
+if doc_targets.length() == 0
+    message = 'No docs targets found'
+else
+    message = 'Built docs:'
+endif
+run_target('dts/doc', command: [echo, message, doc_target_names],
+    depends: doc_targets)
diff --git a/meson.build b/meson.build
index 39cb73846d..4d34dc531c 100644
--- a/meson.build
+++ b/meson.build
@@ -85,6 +85,7 @@  subdir('app')
 
 # build docs
 subdir('doc')
+subdir('dts')
 
 # build any examples explicitly requested - useful for developers - and
 # install any example code into the appropriate install path