[{"id":35247,"web_url":"https://patchwork.libcamera.org/comment/35247/","msgid":"<175388260924.371358.1552825033626055559@localhost>","date":"2025-07-30T13:36:49","subject":"Re: [PATCH v3 3/3] Documentation: Use Sphinx doxylink to generate\n\tlinks to doxygen","submitter":{"id":184,"url":"https://patchwork.libcamera.org/api/people/184/","name":"Stefan Klug","email":"stefan.klug@ideasonboard.com"},"content":"Hi Laurent,\n\nThank you for the patch.\n\nQuoting Laurent Pinchart (2025-07-30 13:50:44)\n> The libcamera Sphinx documentation needs to link to the API\n> documentation generated by Doxygen. The links currently point to the\n> documentation hosted on the official https://libcamera.org/ website.\n> This causes multiple issues:\n> \n> - Doxygen generates URLs with MD5 hashes of function signatures, making\n>   the link targets unstable.\n> \n> - When testing documentation builds that include API changes, links to\n>   new API elements will be broken.\n> \n> - The generated documentation can't be browsed offline.\n> \n> Fix this by using the Sphinx doxylink extension. This allows specifying\n> link targets as class and function names, with the link being\n> automatically generated using the same MD5 hashing as Doxygen. The root\n> of the link target is configured in a central location, which defaults\n> to the build directory and can be overridden to point to the libcamera\n> website when pushing the documentation.\n> \n> This commit only introduces the infrastructure to use doxylink. Manual\n> links will be replaced separately.\n> \n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n> Changes since v2:\n> \n> - Manually check the doxylink version\n> \n> Changes since v1:\n> \n> - Add sphinxcontrib.doxylink to py_modules\n> ---\n>  Documentation/Doxyfile-internal.in |  2 +\n>  Documentation/Doxyfile-public.in   |  2 +\n>  Documentation/conf.py              | 13 +++++-\n>  Documentation/meson.build          | 69 ++++++++++++++++++++----------\n>  README.rst                         |  3 +-\n>  5 files changed, 65 insertions(+), 24 deletions(-)\n> \n> diff --git a/Documentation/Doxyfile-internal.in b/Documentation/Doxyfile-internal.in\n> index 5343bc2b131c..a422bb0719da 100644\n> --- a/Documentation/Doxyfile-internal.in\n> +++ b/Documentation/Doxyfile-internal.in\n> @@ -3,6 +3,8 @@\n>  @INCLUDE_PATH          = @TOP_BUILDDIR@/Documentation\n>  @INCLUDE               = Doxyfile-common\n>  \n> +GENERATE_TAGFILE       = @TOP_BUILDDIR@/Documentation/internal-api-html/tagfile.xml\n> +\n>  HIDE_UNDOC_CLASSES     = NO\n>  HIDE_UNDOC_MEMBERS     = NO\n>  HTML_OUTPUT            = internal-api-html\n> diff --git a/Documentation/Doxyfile-public.in b/Documentation/Doxyfile-public.in\n> index 36bb57584a07..c3a8b0dd003a 100644\n> --- a/Documentation/Doxyfile-public.in\n> +++ b/Documentation/Doxyfile-public.in\n> @@ -3,6 +3,8 @@\n>  @INCLUDE_PATH          = @TOP_BUILDDIR@/Documentation\n>  @INCLUDE               = Doxyfile-common\n>  \n> +GENERATE_TAGFILE       = @TOP_BUILDDIR@/Documentation/api-html/tagfile.xml\n> +\n>  HIDE_UNDOC_CLASSES     = YES\n>  HIDE_UNDOC_MEMBERS     = YES\n>  HTML_OUTPUT            = api-html\n> diff --git a/Documentation/conf.py b/Documentation/conf.py\n> index 870937289eb4..f50be60a1559 100644\n> --- a/Documentation/conf.py\n> +++ b/Documentation/conf.py\n> @@ -37,7 +37,8 @@ author = 'The libcamera documentation authors'\n>  # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n>  # ones.\n>  extensions = [\n> -    'sphinx.ext.graphviz'\n> +    'sphinx.ext.graphviz',\n> +    'sphinxcontrib.doxylink',\n>  ]\n>  \n>  graphviz_output_format = 'svg'\n> @@ -71,6 +72,16 @@ exclude_patterns = [\n>  # The name of the Pygments (syntax highlighting) style to use.\n>  pygments_style = None\n>  \n> +doxylink = {\n> +    'doxy-pub': (\n> +        'Documentation/api-html/tagfile.xml',\n> +        '../api-html/',\n> +    ),\n> +    'doxy-int': (\n> +        'Documentation/internal-api-html/tagfile.xml',\n> +        '../internal-api-html/',\n> +    ),\n> +}\n>  \n>  # -- Options for HTML output -------------------------------------------------\n>  \n> diff --git a/Documentation/meson.build b/Documentation/meson.build\n> index 3afdcc1a87af..b898ba3a0b7e 100644\n> --- a/Documentation/meson.build\n> +++ b/Documentation/meson.build\n> @@ -81,16 +81,16 @@ if doxygen.found() and dot.found()\n>                                   '@INPUT@',\n>                               ])\n>  \n> -    custom_target('doxygen-public',\n> -                  input : [\n> -                      doxyfile,\n> -                      doxyfile_common,\n> -                  ],\n> -                  output : 'api-html',\n> -                  command : [doxygen, doxyfile],\n> -                  install : true,\n> -                  install_dir : doc_install_dir,\n> -                  install_tag : 'doc')\n> +    doxygen_public = custom_target('doxygen-public',\n> +                                   input : [\n> +                                       doxyfile,\n> +                                       doxyfile_common,\n> +                                   ],\n> +                                   output : 'api-html',\n> +                                   command : [doxygen, doxyfile],\n> +                                   install : true,\n> +                                   install_dir : doc_install_dir,\n> +                                   install_tag : 'doc')\n>  \n>      # This is the internal documentation, which hard-codes a list of directories\n>      # to parse in its doxyfile.\n> @@ -99,18 +99,18 @@ if doxygen.found() and dot.found()\n>                                output : 'Doxyfile-internal',\n>                                configuration : cdata)\n>  \n> -    custom_target('doxygen-internal',\n> -                  input : [\n> -                      doxyfile,\n> -                      doxyfile_common,\n> -                      doxygen_public_input,\n> -                      doxygen_internal_input,\n> -                  ],\n> -                  output : 'internal-api-html',\n> -                  command : [doxygen, doxyfile],\n> -                  install : true,\n> -                  install_dir : doc_install_dir,\n> -                  install_tag : 'doc-internal')\n> +    doxygen_internal = custom_target('doxygen-internal',\n> +                                     input : [\n> +                                         doxyfile,\n> +                                         doxyfile_common,\n> +                                         doxygen_public_input,\n> +                                         doxygen_internal_input,\n> +                                     ],\n> +                                     output : 'internal-api-html',\n> +                                     command : [doxygen, doxyfile],\n> +                                     install : true,\n> +                                     install_dir : doc_install_dir,\n> +                                     install_tag : 'doc-internal')\n>  endif\n>  \n>  #\n> @@ -121,6 +121,27 @@ sphinx = find_program('sphinx-build-3', 'sphinx-build',\n>                        required : get_option('documentation'))\n>  \n>  if sphinx.found()\n> +    # Many distributions do not provide a recent-enough version of the doxylink\n> +    # module. This results in a build error with a cryptic message. Check the\n> +    # version manually and print clear error messages.\n> +    py_mod = import('python')\n> +    py3 = py_mod.find_installation('python3')\n> +\n> +    mod = 'sphinxcontrib.doxylink'\n> +    min_version = '>=1.6.1'\n> +    version = run_command(py3, '-c',\n> +                          'import @0@ ; print(@0@.__version__)'.format(mod),\n> +                          check : false).stdout().strip()\n> +\n> +    if version == ''\n> +        error('@0@ module not found'.format(mod))\n> +    endif\n> +\n> +    if not version.version_compare(min_version)\n> +        error('@0@ module v@1@ is too old, @2@ is needed'\n> +              .format(mod, version, min_version))\n> +    endif\n\nUgh that is harder than it should be. Thanks for the error message...\n\nReviewed-by: Stefan Klug <stefan.klug@ideasonboard.com>\n\n> +\n>      docs_sources = [\n>          'camera-sensor-model.rst',\n>          'code-of-conduct.rst',\n> @@ -154,6 +175,10 @@ if sphinx.found()\n>                    input : docs_sources,\n>                    output : 'html',\n>                    build_by_default : true,\n> +                  depends : [\n> +                      doxygen_public,\n> +                      doxygen_internal,\n> +                  ],\n>                    install : true,\n>                    install_dir : doc_install_dir,\n>                    install_tag : 'doc')\n> diff --git a/README.rst b/README.rst\n> index e2a6e275895e..e9a7dd82de74 100644\n> --- a/README.rst\n> +++ b/README.rst\n> @@ -67,7 +67,8 @@ for device hotplug enumeration: [optional]\n>          libudev-dev\n>  \n>  for documentation: [optional]\n> -        python3-sphinx doxygen graphviz texlive-latex-extra\n> +        doxygen graphviz python3-sphinx python3-sphinxcontrib.doxylink (>= 1.6.1)\n> +        texlive-latex-extra\n>  \n>  for gstreamer: [optional]\n>          libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev\n> -- \n> Regards,\n> \n> Laurent Pinchart\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 918A5BDCC1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 30 Jul 2025 13:36:55 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 9E7BD69201;\n\tWed, 30 Jul 2025 15:36:54 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 5BF71691F8\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 30 Jul 2025 15:36:52 +0200 (CEST)","from ideasonboard.com\n\t(p200300f89f4c84ba5ae71567ef55a21c.dip0.t-ipconnect.de\n\t[IPv6:2003:f8:9f4c:84ba:5ae7:1567:ef55:a21c])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id C59BB240A; \n\tWed, 30 Jul 2025 15:36:08 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"TDmkdVAo\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1753882568;\n\tbh=ObK2oQg72P0B/QGWhdW/76Z9P23qtacOOWbUCOYzTbI=;\n\th=In-Reply-To:References:Subject:From:To:Date:From;\n\tb=TDmkdVAo7KwYLjqLNu4yFCRxdRrQAOS2uVmTEpQjLW72ZON2AyDlL2quPD7Bb5gFX\n\tUomQxKLMQNzYBCey4W/wroAVUym91l8/ihpx0Zoo4YpyXXDIZKcGRhAPE2BrogejAD\n\tTxQwNioBWrAw4rgWU/QXtAiDtUkX9zC4F/ljonCI=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20250730115045.3481-4-laurent.pinchart@ideasonboard.com>","References":"<20250730115045.3481-1-laurent.pinchart@ideasonboard.com>\n\t<20250730115045.3481-4-laurent.pinchart@ideasonboard.com>","Subject":"Re: [PATCH v3 3/3] Documentation: Use Sphinx doxylink to generate\n\tlinks to doxygen","From":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Wed, 30 Jul 2025 15:36:49 +0200","Message-ID":"<175388260924.371358.1552825033626055559@localhost>","User-Agent":"alot/0.12.dev8+g2c003385c862.d20250602","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]