[3/4] Documentation: Use Sphinx doxylink to generate links to doxygen
diff mbox series

Message ID 20250727015720.6867-4-laurent.pinchart@ideasonboard.com
State Superseded
Headers show
Series
  • Documentation: Use doxylink to generate Sphinx-to-Doxygen links
Related show

Commit Message

Laurent Pinchart July 27, 2025, 1:57 a.m. UTC
The libcamera Sphinx documentation needs to link to the API
documentation generated by Doxygen. The links currently point to the
documentation hosted on the official https://libcamera.org/ website.
This causes multiple issues:

- Doxygen generates URLs with MD5 hashes of function signatures, making
  the link targets unstable.

- When testing documentation builds that include API changes, links to
  new API elements will be broken.

- The generated documentation can't be browsed offline.

Fix this by using the Sphinx doxylink extension. This allows specifying
link targets as class and function names, with the link being
automatically generated using the same MD5 hashing as Doxygen. The root
of the link target is configured in a central location, which defaults
to the build directory and can be overridden to point to the libcamera
website when pushing the documentation.

This commit only introduces the infrastructure to use doxylink. Manual
links will be replaced separately.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 Documentation/Doxyfile-internal.in |  2 ++
 Documentation/Doxyfile-public.in   |  2 ++
 Documentation/conf.py              | 13 +++++++-
 Documentation/meson.build          | 48 ++++++++++++++++--------------
 README.rst                         |  3 +-
 5 files changed, 44 insertions(+), 24 deletions(-)

Comments

Pőcze Barnabás July 29, 2025, 3:24 p.m. UTC | #1
Hi

2025. 07. 27. 3:57 keltezéssel, Laurent Pinchart írta:
> The libcamera Sphinx documentation needs to link to the API
> documentation generated by Doxygen. The links currently point to the
> documentation hosted on the official https://libcamera.org/ website.
> This causes multiple issues:
> 
> - Doxygen generates URLs with MD5 hashes of function signatures, making
>    the link targets unstable.
> 
> - When testing documentation builds that include API changes, links to
>    new API elements will be broken.
> 
> - The generated documentation can't be browsed offline.
> 
> Fix this by using the Sphinx doxylink extension. This allows specifying
> link targets as class and function names, with the link being
> automatically generated using the same MD5 hashing as Doxygen. The root
> of the link target is configured in a central location, which defaults
> to the build directory and can be overridden to point to the libcamera
> website when pushing the documentation.
> 
> This commit only introduces the infrastructure to use doxylink. Manual
> links will be replaced separately.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>   Documentation/Doxyfile-internal.in |  2 ++
>   Documentation/Doxyfile-public.in   |  2 ++
>   Documentation/conf.py              | 13 +++++++-
>   Documentation/meson.build          | 48 ++++++++++++++++--------------
>   README.rst                         |  3 +-
>   5 files changed, 44 insertions(+), 24 deletions(-)
> 
> diff --git a/Documentation/Doxyfile-internal.in b/Documentation/Doxyfile-internal.in
> index 5343bc2b131c..a422bb0719da 100644
> --- a/Documentation/Doxyfile-internal.in
> +++ b/Documentation/Doxyfile-internal.in
> @@ -3,6 +3,8 @@
>   @INCLUDE_PATH          = @TOP_BUILDDIR@/Documentation
>   @INCLUDE               = Doxyfile-common
> 
> +GENERATE_TAGFILE       = @TOP_BUILDDIR@/Documentation/internal-api-html/tagfile.xml
> +
>   HIDE_UNDOC_CLASSES     = NO
>   HIDE_UNDOC_MEMBERS     = NO
>   HTML_OUTPUT            = internal-api-html
> diff --git a/Documentation/Doxyfile-public.in b/Documentation/Doxyfile-public.in
> index 36bb57584a07..c3a8b0dd003a 100644
> --- a/Documentation/Doxyfile-public.in
> +++ b/Documentation/Doxyfile-public.in
> @@ -3,6 +3,8 @@
>   @INCLUDE_PATH          = @TOP_BUILDDIR@/Documentation
>   @INCLUDE               = Doxyfile-common
> 
> +GENERATE_TAGFILE       = @TOP_BUILDDIR@/Documentation/api-html/tagfile.xml
> +
>   HIDE_UNDOC_CLASSES     = YES
>   HIDE_UNDOC_MEMBERS     = YES
>   HTML_OUTPUT            = api-html
> diff --git a/Documentation/conf.py b/Documentation/conf.py
> index 870937289eb4..f50be60a1559 100644
> --- a/Documentation/conf.py
> +++ b/Documentation/conf.py
> @@ -37,7 +37,8 @@ author = 'The libcamera documentation authors'
>   # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
>   # ones.
>   extensions = [
> -    'sphinx.ext.graphviz'
> +    'sphinx.ext.graphviz',
> +    'sphinxcontrib.doxylink',
>   ]

This was not installed for me, and things failed with a longer error message
than I would have expected. I'm wondering if it might make sense to add something like

diff --git a/Documentation/meson.build b/Documentation/meson.build
index 87918bf9a..a4752f787 100644
--- a/Documentation/meson.build
+++ b/Documentation/meson.build
@@ -167,4 +167,6 @@ if sphinx.found()
                    build_always_stale : true,
                    input : docs_sources,
                    output : 'linkcheck')
+
+    py_modules += 'sphinxcontrib.doxylink'
  endif

so that the there is a clear error message from meson as to what the issue is.
Admittedly I think it could be possible that sphinx does not use the python installation
that is found later by meson. This could be addressed maybe like this:

diff --git a/Documentation/meson.build b/Documentation/meson.build
index 87918bf9a..b74efea6d 100644
--- a/Documentation/meson.build
+++ b/Documentation/meson.build
@@ -117,10 +117,10 @@ endif
  # Sphinx
  #
  
-sphinx = find_program('sphinx-build-3', 'sphinx-build',
-                      required : get_option('documentation'))
+py_mod = import('python')
+py = py_mod.find_installation('python3', required : get_option('documentation'), modules : ['sphinx', 'sphinxcontrib.doxylink'])
  
-if sphinx.found()
+if py.found()
      docs_sources = [
          'camera-sensor-model.rst',
          'code-of-conduct.rst',
@@ -149,7 +149,7 @@ if sphinx.found()
      release = 'release=v' + libcamera_git_version
  
      custom_target('documentation',
-                  command : [sphinx, '-D', release, '-q', '-W', '-b', 'html',
+                  command : [py, '-m', 'sphinx', '-D', release, '-q', '-W', '-b', 'html',
                               meson.current_source_dir(), '@OUTPUT@'],
                    input : docs_sources,
                    output : 'html',
@@ -163,7 +163,7 @@ if sphinx.found()
                    install_tag : 'doc')
  
      custom_target('documentation-linkcheck',
-                  command : [sphinx, '-W', '-b', 'linkcheck', meson.current_source_dir(), '@OUTPUT@'],
+                  command : [py, '-m', 'sphinx', '-W', '-b', 'linkcheck', meson.current_source_dir(), '@OUTPUT@'],
                    build_always_stale : true,
                    input : docs_sources,
                    output : 'linkcheck')


Otherwise the links seem to work nicely.


Regards,
Barnabás Pőcze


> 
>   graphviz_output_format = 'svg'
> @@ -71,6 +72,16 @@ exclude_patterns = [
>   # The name of the Pygments (syntax highlighting) style to use.
>   pygments_style = None
> 
> +doxylink = {
> +    'doxy-pub': (
> +        'Documentation/api-html/tagfile.xml',
> +        '../api-html/',
> +    ),
> +    'doxy-int': (
> +        'Documentation/internal-api-html/tagfile.xml',
> +        '../internal-api-html/',
> +    ),
> +}
> 
>   # -- Options for HTML output -------------------------------------------------
> 
> diff --git a/Documentation/meson.build b/Documentation/meson.build
> index 3afdcc1a87af..87918bf9a921 100644
> --- a/Documentation/meson.build
> +++ b/Documentation/meson.build
> @@ -81,16 +81,16 @@ if doxygen.found() and dot.found()
>                                    '@INPUT@',
>                                ])
> 
> -    custom_target('doxygen-public',
> -                  input : [
> -                      doxyfile,
> -                      doxyfile_common,
> -                  ],
> -                  output : 'api-html',
> -                  command : [doxygen, doxyfile],
> -                  install : true,
> -                  install_dir : doc_install_dir,
> -                  install_tag : 'doc')
> +    doxygen_public = custom_target('doxygen-public',
> +                                   input : [
> +                                       doxyfile,
> +                                       doxyfile_common,
> +                                   ],
> +                                   output : 'api-html',
> +                                   command : [doxygen, doxyfile],
> +                                   install : true,
> +                                   install_dir : doc_install_dir,
> +                                   install_tag : 'doc')
> 
>       # This is the internal documentation, which hard-codes a list of directories
>       # to parse in its doxyfile.
> @@ -99,18 +99,18 @@ if doxygen.found() and dot.found()
>                                 output : 'Doxyfile-internal',
>                                 configuration : cdata)
> 
> -    custom_target('doxygen-internal',
> -                  input : [
> -                      doxyfile,
> -                      doxyfile_common,
> -                      doxygen_public_input,
> -                      doxygen_internal_input,
> -                  ],
> -                  output : 'internal-api-html',
> -                  command : [doxygen, doxyfile],
> -                  install : true,
> -                  install_dir : doc_install_dir,
> -                  install_tag : 'doc-internal')
> +    doxygen_internal = custom_target('doxygen-internal',
> +                                     input : [
> +                                         doxyfile,
> +                                         doxyfile_common,
> +                                         doxygen_public_input,
> +                                         doxygen_internal_input,
> +                                     ],
> +                                     output : 'internal-api-html',
> +                                     command : [doxygen, doxyfile],
> +                                     install : true,
> +                                     install_dir : doc_install_dir,
> +                                     install_tag : 'doc-internal')
>   endif
> 
>   #
> @@ -154,6 +154,10 @@ if sphinx.found()
>                     input : docs_sources,
>                     output : 'html',
>                     build_by_default : true,
> +                  depends : [
> +                      doxygen_public,
> +                      doxygen_internal,
> +                  ],
>                     install : true,
>                     install_dir : doc_install_dir,
>                     install_tag : 'doc')
> diff --git a/README.rst b/README.rst
> index e2a6e275895e..e9a7dd82de74 100644
> --- a/README.rst
> +++ b/README.rst
> @@ -67,7 +67,8 @@ for device hotplug enumeration: [optional]
>           libudev-dev
> 
>   for documentation: [optional]
> -        python3-sphinx doxygen graphviz texlive-latex-extra
> +        doxygen graphviz python3-sphinx python3-sphinxcontrib.doxylink (>= 1.6.1)
> +        texlive-latex-extra
> 
>   for gstreamer: [optional]
>           libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
> --
> Regards,
> 
> Laurent Pinchart
>
Laurent Pinchart July 29, 2025, 8:22 p.m. UTC | #2
On Tue, Jul 29, 2025 at 03:24:12PM +0000, Barnabás Pőcze wrote:
> 2025. 07. 27. 3:57 keltezéssel, Laurent Pinchart írta:
> > The libcamera Sphinx documentation needs to link to the API
> > documentation generated by Doxygen. The links currently point to the
> > documentation hosted on the official https://libcamera.org/ website.
> > This causes multiple issues:
> > 
> > - Doxygen generates URLs with MD5 hashes of function signatures, making
> >    the link targets unstable.
> > 
> > - When testing documentation builds that include API changes, links to
> >    new API elements will be broken.
> > 
> > - The generated documentation can't be browsed offline.
> > 
> > Fix this by using the Sphinx doxylink extension. This allows specifying
> > link targets as class and function names, with the link being
> > automatically generated using the same MD5 hashing as Doxygen. The root
> > of the link target is configured in a central location, which defaults
> > to the build directory and can be overridden to point to the libcamera
> > website when pushing the documentation.
> > 
> > This commit only introduces the infrastructure to use doxylink. Manual
> > links will be replaced separately.
> > 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >   Documentation/Doxyfile-internal.in |  2 ++
> >   Documentation/Doxyfile-public.in   |  2 ++
> >   Documentation/conf.py              | 13 +++++++-
> >   Documentation/meson.build          | 48 ++++++++++++++++--------------
> >   README.rst                         |  3 +-
> >   5 files changed, 44 insertions(+), 24 deletions(-)
> > 
> > diff --git a/Documentation/Doxyfile-internal.in b/Documentation/Doxyfile-internal.in
> > index 5343bc2b131c..a422bb0719da 100644
> > --- a/Documentation/Doxyfile-internal.in
> > +++ b/Documentation/Doxyfile-internal.in
> > @@ -3,6 +3,8 @@
> >   @INCLUDE_PATH          = @TOP_BUILDDIR@/Documentation
> >   @INCLUDE               = Doxyfile-common
> > 
> > +GENERATE_TAGFILE       = @TOP_BUILDDIR@/Documentation/internal-api-html/tagfile.xml
> > +
> >   HIDE_UNDOC_CLASSES     = NO
> >   HIDE_UNDOC_MEMBERS     = NO
> >   HTML_OUTPUT            = internal-api-html
> > diff --git a/Documentation/Doxyfile-public.in b/Documentation/Doxyfile-public.in
> > index 36bb57584a07..c3a8b0dd003a 100644
> > --- a/Documentation/Doxyfile-public.in
> > +++ b/Documentation/Doxyfile-public.in
> > @@ -3,6 +3,8 @@
> >   @INCLUDE_PATH          = @TOP_BUILDDIR@/Documentation
> >   @INCLUDE               = Doxyfile-common
> > 
> > +GENERATE_TAGFILE       = @TOP_BUILDDIR@/Documentation/api-html/tagfile.xml
> > +
> >   HIDE_UNDOC_CLASSES     = YES
> >   HIDE_UNDOC_MEMBERS     = YES
> >   HTML_OUTPUT            = api-html
> > diff --git a/Documentation/conf.py b/Documentation/conf.py
> > index 870937289eb4..f50be60a1559 100644
> > --- a/Documentation/conf.py
> > +++ b/Documentation/conf.py
> > @@ -37,7 +37,8 @@ author = 'The libcamera documentation authors'
> >   # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
> >   # ones.
> >   extensions = [
> > -    'sphinx.ext.graphviz'
> > +    'sphinx.ext.graphviz',
> > +    'sphinxcontrib.doxylink',
> >   ]
> 
> This was not installed for me, and things failed with a longer error message
> than I would have expected.

Here's what I get:

--------
FAILED: Documentation/html
/usr/bin/sphinx-build -D release=v0.5.1+114-79f47d71 -q -W -b html /home/laurent/src/iob/oss/libcamera/libcamera/Documentation Documentation/html

Extension error!

Versions
========

* Platform:         linux; (Linux-6.12.31-gentoo-x86_64-Intel-R-_Core-TM-_i7-8550U_CPU_@_1.80GHz-with-glibc2.41)
* Python version:   3.13.5 (CPython)
* Sphinx version:   8.2.3
* Docutils version: 0.21.2
* Jinja2 version:   3.1.6
* Pygments version: 2.19.2

Last Messages
=============

None.

Loaded Extensions
=================

None.

Traceback
=========

      File "/usr/lib/python3.13/site-packages/sphinx/registry.py", line 544, in load_extension
        raise ExtensionError(
            __('Could not import extension %s') % extname, err
        ) from err
    sphinx.errors.ExtensionError: Could not import extension sphinxcontrib.doxylink (exception: No module named 'sphinxcontrib.doxylink')


The full traceback has been saved in:
/tmp/sphinx-err-lelwc3u2.log

To report this error to the developers, please open an issue at <https://github.com/sphinx-doc/sphinx/issues/>. Thanks!
Please also report this if it was a user error, so that a better error message can be provided next time.
--------

Is your experience similar ?

> I'm wondering if it might make sense to add something like
> 
> diff --git a/Documentation/meson.build b/Documentation/meson.build
> index 87918bf9a..a4752f787 100644
> --- a/Documentation/meson.build
> +++ b/Documentation/meson.build
> @@ -167,4 +167,6 @@ if sphinx.found()
>                     build_always_stale : true,
>                     input : docs_sources,
>                     output : 'linkcheck')
> +
> +    py_modules += 'sphinxcontrib.doxylink'
>   endif
> 
> so that the there is a clear error message from meson as to what the issue is.
> Admittedly I think it could be possible that sphinx does not use the python installation
> that is found later by meson. This could be addressed maybe like this:
> 
> diff --git a/Documentation/meson.build b/Documentation/meson.build
> index 87918bf9a..b74efea6d 100644
> --- a/Documentation/meson.build
> +++ b/Documentation/meson.build
> @@ -117,10 +117,10 @@ endif
>   # Sphinx
>   #
>   
> -sphinx = find_program('sphinx-build-3', 'sphinx-build',
> -                      required : get_option('documentation'))
> +py_mod = import('python')
> +py = py_mod.find_installation('python3', required : get_option('documentation'), modules : ['sphinx', 'sphinxcontrib.doxylink'])
>   
> -if sphinx.found()
> +if py.found()
>       docs_sources = [
>           'camera-sensor-model.rst',
>           'code-of-conduct.rst',
> @@ -149,7 +149,7 @@ if sphinx.found()
>       release = 'release=v' + libcamera_git_version
>   
>       custom_target('documentation',
> -                  command : [sphinx, '-D', release, '-q', '-W', '-b', 'html',
> +                  command : [py, '-m', 'sphinx', '-D', release, '-q', '-W', '-b', 'html',
>                                meson.current_source_dir(), '@OUTPUT@'],
>                     input : docs_sources,
>                     output : 'html',
> @@ -163,7 +163,7 @@ if sphinx.found()
>                     install_tag : 'doc')
>   
>       custom_target('documentation-linkcheck',
> -                  command : [sphinx, '-W', '-b', 'linkcheck', meson.current_source_dir(), '@OUTPUT@'],
> +                  command : [py, '-m', 'sphinx', '-W', '-b', 'linkcheck', meson.current_source_dir(), '@OUTPUT@'],
>                     build_always_stale : true,
>                     input : docs_sources,
>                     output : 'linkcheck')

I'd prefer the first option, based on py_modules, as it's simpler. I
tested it locally (Gentoo, distribution-provided sphinxcontrib.doxylink
version) and in CI (Debian 11, 12 and 13, with a mix if PyPI and
distribution packages) without issues. We can always improve it later if
needed.

> Otherwise the links seem to work nicely.

Thanks for testing. I'll send a v2 with py_modules.

> >   graphviz_output_format = 'svg'
> > @@ -71,6 +72,16 @@ exclude_patterns = [
> >   # The name of the Pygments (syntax highlighting) style to use.
> >   pygments_style = None
> > 
> > +doxylink = {
> > +    'doxy-pub': (
> > +        'Documentation/api-html/tagfile.xml',
> > +        '../api-html/',
> > +    ),
> > +    'doxy-int': (
> > +        'Documentation/internal-api-html/tagfile.xml',
> > +        '../internal-api-html/',
> > +    ),
> > +}
> > 
> >   # -- Options for HTML output -------------------------------------------------
> > 
> > diff --git a/Documentation/meson.build b/Documentation/meson.build
> > index 3afdcc1a87af..87918bf9a921 100644
> > --- a/Documentation/meson.build
> > +++ b/Documentation/meson.build
> > @@ -81,16 +81,16 @@ if doxygen.found() and dot.found()
> >                                    '@INPUT@',
> >                                ])
> > 
> > -    custom_target('doxygen-public',
> > -                  input : [
> > -                      doxyfile,
> > -                      doxyfile_common,
> > -                  ],
> > -                  output : 'api-html',
> > -                  command : [doxygen, doxyfile],
> > -                  install : true,
> > -                  install_dir : doc_install_dir,
> > -                  install_tag : 'doc')
> > +    doxygen_public = custom_target('doxygen-public',
> > +                                   input : [
> > +                                       doxyfile,
> > +                                       doxyfile_common,
> > +                                   ],
> > +                                   output : 'api-html',
> > +                                   command : [doxygen, doxyfile],
> > +                                   install : true,
> > +                                   install_dir : doc_install_dir,
> > +                                   install_tag : 'doc')
> > 
> >       # This is the internal documentation, which hard-codes a list of directories
> >       # to parse in its doxyfile.
> > @@ -99,18 +99,18 @@ if doxygen.found() and dot.found()
> >                                 output : 'Doxyfile-internal',
> >                                 configuration : cdata)
> > 
> > -    custom_target('doxygen-internal',
> > -                  input : [
> > -                      doxyfile,
> > -                      doxyfile_common,
> > -                      doxygen_public_input,
> > -                      doxygen_internal_input,
> > -                  ],
> > -                  output : 'internal-api-html',
> > -                  command : [doxygen, doxyfile],
> > -                  install : true,
> > -                  install_dir : doc_install_dir,
> > -                  install_tag : 'doc-internal')
> > +    doxygen_internal = custom_target('doxygen-internal',
> > +                                     input : [
> > +                                         doxyfile,
> > +                                         doxyfile_common,
> > +                                         doxygen_public_input,
> > +                                         doxygen_internal_input,
> > +                                     ],
> > +                                     output : 'internal-api-html',
> > +                                     command : [doxygen, doxyfile],
> > +                                     install : true,
> > +                                     install_dir : doc_install_dir,
> > +                                     install_tag : 'doc-internal')
> >   endif
> > 
> >   #
> > @@ -154,6 +154,10 @@ if sphinx.found()
> >                     input : docs_sources,
> >                     output : 'html',
> >                     build_by_default : true,
> > +                  depends : [
> > +                      doxygen_public,
> > +                      doxygen_internal,
> > +                  ],
> >                     install : true,
> >                     install_dir : doc_install_dir,
> >                     install_tag : 'doc')
> > diff --git a/README.rst b/README.rst
> > index e2a6e275895e..e9a7dd82de74 100644
> > --- a/README.rst
> > +++ b/README.rst
> > @@ -67,7 +67,8 @@ for device hotplug enumeration: [optional]
> >           libudev-dev
> > 
> >   for documentation: [optional]
> > -        python3-sphinx doxygen graphviz texlive-latex-extra
> > +        doxygen graphviz python3-sphinx python3-sphinxcontrib.doxylink (>= 1.6.1)
> > +        texlive-latex-extra
> > 
> >   for gstreamer: [optional]
> >           libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
Barnabás Pőcze July 30, 2025, 7:35 a.m. UTC | #3
Hi

2025. 07. 29. 22:22 keltezéssel, Laurent Pinchart írta:
> On Tue, Jul 29, 2025 at 03:24:12PM +0000, Barnabás Pőcze wrote:
>> 2025. 07. 27. 3:57 keltezéssel, Laurent Pinchart írta:
>>> The libcamera Sphinx documentation needs to link to the API
>>> documentation generated by Doxygen. The links currently point to the
>>> documentation hosted on the official https://libcamera.org/ website.
>>> This causes multiple issues:
>>>
>>> - Doxygen generates URLs with MD5 hashes of function signatures, making
>>>     the link targets unstable.
>>>
>>> - When testing documentation builds that include API changes, links to
>>>     new API elements will be broken.
>>>
>>> - The generated documentation can't be browsed offline.
>>>
>>> Fix this by using the Sphinx doxylink extension. This allows specifying
>>> link targets as class and function names, with the link being
>>> automatically generated using the same MD5 hashing as Doxygen. The root
>>> of the link target is configured in a central location, which defaults
>>> to the build directory and can be overridden to point to the libcamera
>>> website when pushing the documentation.
>>>
>>> This commit only introduces the infrastructure to use doxylink. Manual
>>> links will be replaced separately.
>>>
>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>> ---
>>>    Documentation/Doxyfile-internal.in |  2 ++
>>>    Documentation/Doxyfile-public.in   |  2 ++
>>>    Documentation/conf.py              | 13 +++++++-
>>>    Documentation/meson.build          | 48 ++++++++++++++++--------------
>>>    README.rst                         |  3 +-
>>>    5 files changed, 44 insertions(+), 24 deletions(-)
>>>
>>> diff --git a/Documentation/Doxyfile-internal.in b/Documentation/Doxyfile-internal.in
>>> index 5343bc2b131c..a422bb0719da 100644
>>> --- a/Documentation/Doxyfile-internal.in
>>> +++ b/Documentation/Doxyfile-internal.in
>>> @@ -3,6 +3,8 @@
>>>    @INCLUDE_PATH          = @TOP_BUILDDIR@/Documentation
>>>    @INCLUDE               = Doxyfile-common
>>>
>>> +GENERATE_TAGFILE       = @TOP_BUILDDIR@/Documentation/internal-api-html/tagfile.xml
>>> +
>>>    HIDE_UNDOC_CLASSES     = NO
>>>    HIDE_UNDOC_MEMBERS     = NO
>>>    HTML_OUTPUT            = internal-api-html
>>> diff --git a/Documentation/Doxyfile-public.in b/Documentation/Doxyfile-public.in
>>> index 36bb57584a07..c3a8b0dd003a 100644
>>> --- a/Documentation/Doxyfile-public.in
>>> +++ b/Documentation/Doxyfile-public.in
>>> @@ -3,6 +3,8 @@
>>>    @INCLUDE_PATH          = @TOP_BUILDDIR@/Documentation
>>>    @INCLUDE               = Doxyfile-common
>>>
>>> +GENERATE_TAGFILE       = @TOP_BUILDDIR@/Documentation/api-html/tagfile.xml
>>> +
>>>    HIDE_UNDOC_CLASSES     = YES
>>>    HIDE_UNDOC_MEMBERS     = YES
>>>    HTML_OUTPUT            = api-html
>>> diff --git a/Documentation/conf.py b/Documentation/conf.py
>>> index 870937289eb4..f50be60a1559 100644
>>> --- a/Documentation/conf.py
>>> +++ b/Documentation/conf.py
>>> @@ -37,7 +37,8 @@ author = 'The libcamera documentation authors'
>>>    # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
>>>    # ones.
>>>    extensions = [
>>> -    'sphinx.ext.graphviz'
>>> +    'sphinx.ext.graphviz',
>>> +    'sphinxcontrib.doxylink',
>>>    ]
>>
>> This was not installed for me, and things failed with a longer error message
>> than I would have expected.
> 
> Here's what I get:
> 
> --------
> FAILED: Documentation/html
> /usr/bin/sphinx-build -D release=v0.5.1+114-79f47d71 -q -W -b html /home/laurent/src/iob/oss/libcamera/libcamera/Documentation Documentation/html
> 
> Extension error!
> 
> Versions
> ========
> 
> * Platform:         linux; (Linux-6.12.31-gentoo-x86_64-Intel-R-_Core-TM-_i7-8550U_CPU_@_1.80GHz-with-glibc2.41)
> * Python version:   3.13.5 (CPython)
> * Sphinx version:   8.2.3
> * Docutils version: 0.21.2
> * Jinja2 version:   3.1.6
> * Pygments version: 2.19.2
> 
> Last Messages
> =============
> 
> None.
> 
> Loaded Extensions
> =================
> 
> None.
> 
> Traceback
> =========
> 
>        File "/usr/lib/python3.13/site-packages/sphinx/registry.py", line 544, in load_extension
>          raise ExtensionError(
>              __('Could not import extension %s') % extname, err
>          ) from err
>      sphinx.errors.ExtensionError: Could not import extension sphinxcontrib.doxylink (exception: No module named 'sphinxcontrib.doxylink')
> 
> 
> The full traceback has been saved in:
> /tmp/sphinx-err-lelwc3u2.log
> 
> To report this error to the developers, please open an issue at <https://github.com/sphinx-doc/sphinx/issues/>. Thanks!
> Please also report this if it was a user error, so that a better error message can be provided next time.
> --------
> 
> Is your experience similar ?

I get the same thing.


Regards,
Barnabás Pőcze


> 
>> I'm wondering if it might make sense to add something like
>>
>> diff --git a/Documentation/meson.build b/Documentation/meson.build
>> index 87918bf9a..a4752f787 100644
>> --- a/Documentation/meson.build
>> +++ b/Documentation/meson.build
>> @@ -167,4 +167,6 @@ if sphinx.found()
>>                      build_always_stale : true,
>>                      input : docs_sources,
>>                      output : 'linkcheck')
>> +
>> +    py_modules += 'sphinxcontrib.doxylink'
>>    endif
>>
>> so that the there is a clear error message from meson as to what the issue is.
>> Admittedly I think it could be possible that sphinx does not use the python installation
>> that is found later by meson. This could be addressed maybe like this:
>>
>> diff --git a/Documentation/meson.build b/Documentation/meson.build
>> index 87918bf9a..b74efea6d 100644
>> --- a/Documentation/meson.build
>> +++ b/Documentation/meson.build
>> @@ -117,10 +117,10 @@ endif
>>    # Sphinx
>>    #
>>    
>> -sphinx = find_program('sphinx-build-3', 'sphinx-build',
>> -                      required : get_option('documentation'))
>> +py_mod = import('python')
>> +py = py_mod.find_installation('python3', required : get_option('documentation'), modules : ['sphinx', 'sphinxcontrib.doxylink'])
>>    
>> -if sphinx.found()
>> +if py.found()
>>        docs_sources = [
>>            'camera-sensor-model.rst',
>>            'code-of-conduct.rst',
>> @@ -149,7 +149,7 @@ if sphinx.found()
>>        release = 'release=v' + libcamera_git_version
>>    
>>        custom_target('documentation',
>> -                  command : [sphinx, '-D', release, '-q', '-W', '-b', 'html',
>> +                  command : [py, '-m', 'sphinx', '-D', release, '-q', '-W', '-b', 'html',
>>                                 meson.current_source_dir(), '@OUTPUT@'],
>>                      input : docs_sources,
>>                      output : 'html',
>> @@ -163,7 +163,7 @@ if sphinx.found()
>>                      install_tag : 'doc')
>>    
>>        custom_target('documentation-linkcheck',
>> -                  command : [sphinx, '-W', '-b', 'linkcheck', meson.current_source_dir(), '@OUTPUT@'],
>> +                  command : [py, '-m', 'sphinx', '-W', '-b', 'linkcheck', meson.current_source_dir(), '@OUTPUT@'],
>>                      build_always_stale : true,
>>                      input : docs_sources,
>>                      output : 'linkcheck')
> 
> I'd prefer the first option, based on py_modules, as it's simpler. I
> tested it locally (Gentoo, distribution-provided sphinxcontrib.doxylink
> version) and in CI (Debian 11, 12 and 13, with a mix if PyPI and
> distribution packages) without issues. We can always improve it later if
> needed.
> 
>> Otherwise the links seem to work nicely.
> 
> Thanks for testing. I'll send a v2 with py_modules.
> 
>>>    graphviz_output_format = 'svg'
>>> @@ -71,6 +72,16 @@ exclude_patterns = [
>>>    # The name of the Pygments (syntax highlighting) style to use.
>>>    pygments_style = None
>>>
>>> +doxylink = {
>>> +    'doxy-pub': (
>>> +        'Documentation/api-html/tagfile.xml',
>>> +        '../api-html/',
>>> +    ),
>>> +    'doxy-int': (
>>> +        'Documentation/internal-api-html/tagfile.xml',
>>> +        '../internal-api-html/',
>>> +    ),
>>> +}
>>>
>>>    # -- Options for HTML output -------------------------------------------------
>>>
>>> diff --git a/Documentation/meson.build b/Documentation/meson.build
>>> index 3afdcc1a87af..87918bf9a921 100644
>>> --- a/Documentation/meson.build
>>> +++ b/Documentation/meson.build
>>> @@ -81,16 +81,16 @@ if doxygen.found() and dot.found()
>>>                                     '@INPUT@',
>>>                                 ])
>>>
>>> -    custom_target('doxygen-public',
>>> -                  input : [
>>> -                      doxyfile,
>>> -                      doxyfile_common,
>>> -                  ],
>>> -                  output : 'api-html',
>>> -                  command : [doxygen, doxyfile],
>>> -                  install : true,
>>> -                  install_dir : doc_install_dir,
>>> -                  install_tag : 'doc')
>>> +    doxygen_public = custom_target('doxygen-public',
>>> +                                   input : [
>>> +                                       doxyfile,
>>> +                                       doxyfile_common,
>>> +                                   ],
>>> +                                   output : 'api-html',
>>> +                                   command : [doxygen, doxyfile],
>>> +                                   install : true,
>>> +                                   install_dir : doc_install_dir,
>>> +                                   install_tag : 'doc')
>>>
>>>        # This is the internal documentation, which hard-codes a list of directories
>>>        # to parse in its doxyfile.
>>> @@ -99,18 +99,18 @@ if doxygen.found() and dot.found()
>>>                                  output : 'Doxyfile-internal',
>>>                                  configuration : cdata)
>>>
>>> -    custom_target('doxygen-internal',
>>> -                  input : [
>>> -                      doxyfile,
>>> -                      doxyfile_common,
>>> -                      doxygen_public_input,
>>> -                      doxygen_internal_input,
>>> -                  ],
>>> -                  output : 'internal-api-html',
>>> -                  command : [doxygen, doxyfile],
>>> -                  install : true,
>>> -                  install_dir : doc_install_dir,
>>> -                  install_tag : 'doc-internal')
>>> +    doxygen_internal = custom_target('doxygen-internal',
>>> +                                     input : [
>>> +                                         doxyfile,
>>> +                                         doxyfile_common,
>>> +                                         doxygen_public_input,
>>> +                                         doxygen_internal_input,
>>> +                                     ],
>>> +                                     output : 'internal-api-html',
>>> +                                     command : [doxygen, doxyfile],
>>> +                                     install : true,
>>> +                                     install_dir : doc_install_dir,
>>> +                                     install_tag : 'doc-internal')
>>>    endif
>>>
>>>    #
>>> @@ -154,6 +154,10 @@ if sphinx.found()
>>>                      input : docs_sources,
>>>                      output : 'html',
>>>                      build_by_default : true,
>>> +                  depends : [
>>> +                      doxygen_public,
>>> +                      doxygen_internal,
>>> +                  ],
>>>                      install : true,
>>>                      install_dir : doc_install_dir,
>>>                      install_tag : 'doc')
>>> diff --git a/README.rst b/README.rst
>>> index e2a6e275895e..e9a7dd82de74 100644
>>> --- a/README.rst
>>> +++ b/README.rst
>>> @@ -67,7 +67,8 @@ for device hotplug enumeration: [optional]
>>>            libudev-dev
>>>
>>>    for documentation: [optional]
>>> -        python3-sphinx doxygen graphviz texlive-latex-extra
>>> +        doxygen graphviz python3-sphinx python3-sphinxcontrib.doxylink (>= 1.6.1)
>>> +        texlive-latex-extra
>>>
>>>    for gstreamer: [optional]
>>>            libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
>

Patch
diff mbox series

diff --git a/Documentation/Doxyfile-internal.in b/Documentation/Doxyfile-internal.in
index 5343bc2b131c..a422bb0719da 100644
--- a/Documentation/Doxyfile-internal.in
+++ b/Documentation/Doxyfile-internal.in
@@ -3,6 +3,8 @@ 
 @INCLUDE_PATH          = @TOP_BUILDDIR@/Documentation
 @INCLUDE               = Doxyfile-common
 
+GENERATE_TAGFILE       = @TOP_BUILDDIR@/Documentation/internal-api-html/tagfile.xml
+
 HIDE_UNDOC_CLASSES     = NO
 HIDE_UNDOC_MEMBERS     = NO
 HTML_OUTPUT            = internal-api-html
diff --git a/Documentation/Doxyfile-public.in b/Documentation/Doxyfile-public.in
index 36bb57584a07..c3a8b0dd003a 100644
--- a/Documentation/Doxyfile-public.in
+++ b/Documentation/Doxyfile-public.in
@@ -3,6 +3,8 @@ 
 @INCLUDE_PATH          = @TOP_BUILDDIR@/Documentation
 @INCLUDE               = Doxyfile-common
 
+GENERATE_TAGFILE       = @TOP_BUILDDIR@/Documentation/api-html/tagfile.xml
+
 HIDE_UNDOC_CLASSES     = YES
 HIDE_UNDOC_MEMBERS     = YES
 HTML_OUTPUT            = api-html
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 870937289eb4..f50be60a1559 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -37,7 +37,8 @@  author = 'The libcamera documentation authors'
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
-    'sphinx.ext.graphviz'
+    'sphinx.ext.graphviz',
+    'sphinxcontrib.doxylink',
 ]
 
 graphviz_output_format = 'svg'
@@ -71,6 +72,16 @@  exclude_patterns = [
 # The name of the Pygments (syntax highlighting) style to use.
 pygments_style = None
 
+doxylink = {
+    'doxy-pub': (
+        'Documentation/api-html/tagfile.xml',
+        '../api-html/',
+    ),
+    'doxy-int': (
+        'Documentation/internal-api-html/tagfile.xml',
+        '../internal-api-html/',
+    ),
+}
 
 # -- Options for HTML output -------------------------------------------------
 
diff --git a/Documentation/meson.build b/Documentation/meson.build
index 3afdcc1a87af..87918bf9a921 100644
--- a/Documentation/meson.build
+++ b/Documentation/meson.build
@@ -81,16 +81,16 @@  if doxygen.found() and dot.found()
                                  '@INPUT@',
                              ])
 
-    custom_target('doxygen-public',
-                  input : [
-                      doxyfile,
-                      doxyfile_common,
-                  ],
-                  output : 'api-html',
-                  command : [doxygen, doxyfile],
-                  install : true,
-                  install_dir : doc_install_dir,
-                  install_tag : 'doc')
+    doxygen_public = custom_target('doxygen-public',
+                                   input : [
+                                       doxyfile,
+                                       doxyfile_common,
+                                   ],
+                                   output : 'api-html',
+                                   command : [doxygen, doxyfile],
+                                   install : true,
+                                   install_dir : doc_install_dir,
+                                   install_tag : 'doc')
 
     # This is the internal documentation, which hard-codes a list of directories
     # to parse in its doxyfile.
@@ -99,18 +99,18 @@  if doxygen.found() and dot.found()
                               output : 'Doxyfile-internal',
                               configuration : cdata)
 
-    custom_target('doxygen-internal',
-                  input : [
-                      doxyfile,
-                      doxyfile_common,
-                      doxygen_public_input,
-                      doxygen_internal_input,
-                  ],
-                  output : 'internal-api-html',
-                  command : [doxygen, doxyfile],
-                  install : true,
-                  install_dir : doc_install_dir,
-                  install_tag : 'doc-internal')
+    doxygen_internal = custom_target('doxygen-internal',
+                                     input : [
+                                         doxyfile,
+                                         doxyfile_common,
+                                         doxygen_public_input,
+                                         doxygen_internal_input,
+                                     ],
+                                     output : 'internal-api-html',
+                                     command : [doxygen, doxyfile],
+                                     install : true,
+                                     install_dir : doc_install_dir,
+                                     install_tag : 'doc-internal')
 endif
 
 #
@@ -154,6 +154,10 @@  if sphinx.found()
                   input : docs_sources,
                   output : 'html',
                   build_by_default : true,
+                  depends : [
+                      doxygen_public,
+                      doxygen_internal,
+                  ],
                   install : true,
                   install_dir : doc_install_dir,
                   install_tag : 'doc')
diff --git a/README.rst b/README.rst
index e2a6e275895e..e9a7dd82de74 100644
--- a/README.rst
+++ b/README.rst
@@ -67,7 +67,8 @@  for device hotplug enumeration: [optional]
         libudev-dev
 
 for documentation: [optional]
-        python3-sphinx doxygen graphviz texlive-latex-extra
+        doxygen graphviz python3-sphinx python3-sphinxcontrib.doxylink (>= 1.6.1)
+        texlive-latex-extra
 
 for gstreamer: [optional]
         libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev