doxygen icon indicating copy to clipboard operation
doxygen copied to clipboard

Cmake: Documenting cmake script

Open LecrisUT opened this issue 2 years ago • 20 comments

I am sorry if this is already implemented, but I couldn't see it in the issues or the list of supported languages.


Description

Being able to generate xml and html documentation for cmake entities. These would be particularly useful with sphinx+breathe and moderncmakedomain

Details

A minimal documentation that would be helpful for the user documentation:

  • [ ] macro() and function(): positional arguments, named arguments (from cmake_parsed_arguments), and expected variables (set from the parent when function/macro is called)
  • [ ] option() and set(CACHE): type, default, helper string
  • [ ] target: type, aliases, exported name, generated object (library and binary) if any
  • [ ] install(COMPONENT): component, installed items
  • [ ] install(FILES): files, destination
  • [ ] export(TARGET) (part of target above)
  • [ ] find_package(COMPONENT): component, target file

Additionally I think these are helpful for the developer documentation:

  • [ ] add_custom_command(): output files/target, commands, comment
  • [ ] add_custom_target(): target, commands, comment
  • [ ] include(): location (e.g. my_helper.cmake)

LecrisUT avatar Mar 10 '23 15:03 LecrisUT

When I understand it correctly you want to be able to document cmake files and present them in a way doxygen does.

There is no default support for these kind of files in doxygen, you probably can get some good results using a filter and translate the cmake files to something doxygen understands (like C++ code), with settings like INPUT_FILTER etc. this might be possible.

albert-github avatar Mar 10 '23 17:03 albert-github

When I understand it correctly you want to be able to document cmake files and present them in a way doxygen does.

Indeed that's the case. We cannot map all of the cmake entities to C++ ones, but if we can define other custom entities in doxygen, maybe we can find some abstract way of building them. At least all of the definition syntax is statically defined, so it should be possible to connect the doxygen comments above a add_custom_command() for example.

LecrisUT avatar Mar 10 '23 17:03 LecrisUT

Maybe you could give an example with the results you got so far and the problems you still encounter i.e.:

  • attach a, small, self contained example (source+configuration file in a, compressed, tar or zip file) that allows us to see and reproduce the problem? Please don't add external links as they might not be persistent.
  • specify the full doxygen version used (doxygen -v).

albert-github avatar Mar 10 '23 17:03 albert-github

Ok, I will make a small example project to mock-up these, but conceptually here is a short snippet of what I am referring to, just so we can confirm we are on the same page:

### This is a description of `my_function`
### 
### @param _inp1 A positional argument
### @param VAR2 A named argument
function(my_function _inp1)
  cmake_parse_arguments(ARGS "" "VAR2" "" ${ARGN})
  ...
endfunction()

LecrisUT avatar Mar 10 '23 17:03 LecrisUT

(Just some nitpicking)

  1. The : should not be present in the @param statement for the argument name
  2. VAR2 is not an argument of the function

albert-github avatar Mar 10 '23 17:03 albert-github

  1. VAR2 is not an argument of the function

Actually that's the thing about cmake. VAR2 is an argument of my_function because it is called via:

my_function(${positional_argument} VAR2 ${named_argument}) 

Initially I think it is ok if these are passed manually because these need to be parsed, e.g. finding the cmake_parse_arguments() expanding any definitions defined from set(), etc. It might be possible to use cmake-file-api to retrieve these, but I haven't researched this.

LecrisUT avatar Mar 10 '23 18:03 LecrisUT

When I'm not mistaken the @param VAR2 will throw a warning as doxygen doesn't know about the argument VAR2 but it will show in the documentation.

albert-github avatar Mar 10 '23 18:03 albert-github

Well, I've tried to to trick doxygen to parse it as a different language, and unsurprisingly it failed spectacularly:

Input file

Filename: test.cmake

### This is a description of `my_function`
###
### @param _inp1 A positional argument
### @param VAR2 A named argument
/// This is a description of `my_function`
///
/// @param _inp1 A positional argument
/// @param VAR2 A named argument
function(my_function _inp1)
	cmake_parse_arguments(ARGS "" "VAR2" "" ${ARGN})
endfunction()
Doxyfile (Relevant parts)
GENERATE_XML = YES
INPUT                  = cmake/test.cmake
EXTENSION_MAPPING      = cmake=c++
Generated xml
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<doxygen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="compound.xsd" version="1.9.5" xml:lang="en-US">
  <compounddef id="test_8cmake" kind="file" language="C++">
    <compoundname>test.cmake</compoundname>
      <sectiondef kind="func">
      <memberdef kind="function" id="test_8cmake_1a44019289305eb00ccec292780743b509" prot="public" static="no" const="no" explicit="no" inline="no" virt="non-virtual">
        <type></type>
        <definition>function</definition>
        <argsstring>(my_function _inp1) cmake_parse_arguments(ARGS &quot;&quot; &quot;VAR2&quot; &quot;&quot; $</argsstring>
        <name>function</name>
        <param>
          <type>my_function</type>
          <declname>_inp1</declname>
        </param>
        <briefdescription>
        </briefdescription>
        <detaileddescription>
<para>This is a description of <computeroutput>my_function</computeroutput></para>
<para><parameterlist kind="param"><parameteritem>
<parameternamelist>
<parametername>_inp1</parametername>
</parameternamelist>
<parameterdescription>
<para>A positional argument </para>
</parameterdescription>
</parameteritem>
<parameteritem>
<parameternamelist>
<parametername>VAR2</parametername>
</parameternamelist>
<parameterdescription>
<para>A named argument </para>
</parameterdescription>
</parameteritem>
</parameterlist>
</para>
        </detaileddescription>
        <inbodydescription>
        </inbodydescription>
        <location file="cmake/test.cmake" line="9" column="1" bodyfile="cmake/test.cmake" bodystart="9" bodyend="10"/>
      </memberdef>
      </sectiondef>
    <briefdescription>
    </briefdescription>
    <detaileddescription>
    </detaileddescription>
    <programlisting>
<codeline lineno="1"><highlight class="preprocessor">###<sp/>This<sp/>is<sp/>a<sp/>description<sp/>of<sp/>`my_function`</highlight><highlight class="normal"></highlight></codeline>
<codeline lineno="2"><highlight class="normal"></highlight><highlight class="preprocessor">###</highlight><highlight class="normal"></highlight></codeline>
<codeline lineno="3"><highlight class="normal"></highlight><highlight class="preprocessor">###<sp/>@param<sp/>_inp1<sp/>A<sp/>positional<sp/>argument</highlight><highlight class="normal"></highlight></codeline>
<codeline lineno="4"><highlight class="normal"></highlight><highlight class="preprocessor">###<sp/>@param<sp/>VAR2<sp/>A<sp/>named<sp/>argument</highlight></codeline>
<codeline lineno="9"><highlight class="normal">function(my_function<sp/>_inp1)</highlight></codeline>
<codeline lineno="10"><highlight class="normal"><sp/><sp/><sp/><sp/>cmake_parse_arguments(ARGS<sp/></highlight><highlight class="stringliteral">&quot;&quot;</highlight><highlight class="normal"><sp/></highlight><highlight class="stringliteral">&quot;VAR2&quot;</highlight><highlight class="normal"><sp/></highlight><highlight class="stringliteral">&quot;&quot;</highlight><highlight class="normal"><sp/>${ARGN})</highlight></codeline>
<codeline lineno="11"><highlight class="normal">endfunction()</highlight></codeline>
    </programlisting>
    <location file="cmake/test.cmake"/>
  </compounddef>
</doxygen>

So there are a few things to fix:

  • [ ] Use the cmake comment token: #
  • [ ] Change the parsing to extract the type/definition from the function caller, i.e. add_custom_target() -> target. There is no type prefix like def my_function: in python
  • [ ] Parse bodystart, bodyend accordingly (look for endfunction(), endmacro() or closing parenthesis ) )
  • [ ] Change language in compounddef to cmake
  • [ ] Disable argstring or compute it including cmake_parse_argument or cmake-file-api (currently not possible there either)
  • [ ] Disable or fix the code highlighter

LecrisUT avatar Mar 13 '23 13:03 LecrisUT

In the https://github.com/doxygen/doxygen/issues/9906#issuecomment-1464114535I mentioned:

There is no default support for these kind of files in doxygen, you probably can get some good results using a filter and translate the cmake files to something doxygen understands (like C++ code), with settings like INPUT_FILTER etc. this might be possible.

but in the presented "Doxyfile (Relevant parts)" (better to use the outcome of doxygen -x Doxyfile as this shows the differences with the default settings), there is nothing about a filter so doxygen sees the cmake file as C++ code and the cmake file is no C++ code. You will probably have had some warnings as well.

albert-github avatar Mar 13 '23 13:03 albert-github

There is EXTENSION_MAPPING to map it to c++ code, and that is why it picks up at /// "comments". But C++ parser is ill-suited for building on top of this because:

  • It has different comment tokens
  • It requires a type prefix: i.e. void in void my_function(), class in class my_class, etc.

You will probably have had some warnings as well.

Of course there were warnings like that, and surprisingly only for VAR2, even though _inp1 should also be ill defined there.

LecrisUT avatar Mar 13 '23 13:03 LecrisUT

When having cmake code like:

### This is a description of `my_function`
### 
### @param _inp1 A positional argument
### @param VAR2 A named argument
function(my_function _inp1)
  cmake_parse_arguments(ARGS "" "VAR2" "" ${ARGN})
  ...
endfunction()

this would be in C++ terms something like:

/// This is a description of `my_function`
///
/// @param _inp1 A positional argument
/// @param VAR2 A named argument
void my_function(char* _inp1)
  cmake_parse_arguments(ARGS "" "VAR2" "" ${ARGN});
  ...
}

and this should be done by the filter (probably the filter should even do more).

albert-github avatar Mar 13 '23 13:03 albert-github

Doxygen does not have any parser for cmake, so it does not know how to handle any of this. If I use the first version, no xml output is produced at all. There is no cmake related filter:

  • Not in FILE_PATTERNS because it is not built-in
  • INPUT_FILTER requires a custom parser

But even if we change it to be c++ like function, that doesn't solve the main issues that a new language format needs to be defined in order to address all of the other checkboxed issues. For example, how do we convert option() or set(CACHE). How about add_library() and find_package()?

LecrisUT avatar Mar 13 '23 14:03 LecrisUT

You have to write the filter yourself, doxygen doesn't provide the filter just the possibility to use it.

albert-github avatar Mar 13 '23 14:03 albert-github

Again, INPUT_FILTER is not appropriate due to how different these are. The xml output format is fine and useful to output to breathe to generate documentations, but the parser itself needs to be reformulated. There are no return type, you should distinguish between option(), set(CACHE), set(PARENT_SCOPE), etc.

LecrisUT avatar Mar 13 '23 14:03 LecrisUT

Did you try a filter? If so please show the filter, the used input code and the used settings (i.e. the result of doxygen -x Doxyfile) as well as the used doxygen version (doxygen -v) all in a self contained example (source+configuration file in a, compressed, tar or zip file) that allows us to reproduce the problem? Please don't add external links as they might not be persistent.

albert-github avatar Mar 13 '23 14:03 albert-github

No, I did not write a filter because: how do we translate a option() to c++ like entity, how do we add additional important information to be exported, e.g. CACHE or PARENT_SCOPE export type. The design needs to be discussed first. And I am suggesting that a new language needs to be defined instead of writing a filter.

LecrisUT avatar Mar 13 '23 14:03 LecrisUT

I would welcome such a feature. As a workaround, I always use now this cmake module: https://github.com/ferdymercury/cmake-modules/blob/main/make_documentation.cmakewhich parses the file and creates a dox. I use it to document all cmake options. I create the table by hand in the file header, as a comment, normally duplicating the info already written in the help tag of cmake-option command. Such documentation is essential for developers to know what flags they can configure while building.

But a builtin solution would be cleaner, so that it would directly parse the options instead of having to copy paste so much.

Below one example of how i do it:

## CMAKE_DOCUMENTATION_START CMakeLists.txt
##
## Main CMakeFile for compiling MyProjectName.
## Following variables can be configured when running ccmake:
## <table>
## <caption id="config-cmake">Table of configurable CMake parameters</caption>
## <tr><th>Variable             <th>Values                  <th>Description
## <tr><td>BUILD_DOCUMENTATION  <td>ON (OFF)                <td>Build Doxygen HTML documentation
## <tr><td>CLI11_DIR            <td>/opt/CLI11              <td>CLI11 git repository
## <tr><td>CMAKE_BUILD_TYPE     <td>Release (Debug)         <td>Choose the type of build
## <tr><td>ENABLE_TESTING       <td>ON (OFF)                <td>Build CTests
## <tr><td>QHG_LOCATION         <td>qhelpgenerator          <td>Path to qhelpgenerator
## <tr><td>ROOT_DIR             <td>$ROOTSYS (/opt/root)    <td>ROOT build directory
## </table>
##
## CMAKE_DOCUMENTATION_END

option(BUILD_DOCUMENTATION "Create and install the HTML based API documentation (requires Doxygen)" ON)
#and all the other options here duplicated, etc, other stuff...

     add_custom_target(dox
      #DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/doxygen.stamp
      DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
    )
    include(make_documentation)
    PARSE_CMAKE_DOCUMENTATION(INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt" EXCLUDES "${CMAKE_CURRENT_BINARY_DIR}/*")
    WRITE_CMAKE_DOCUMENTATION( "${CMAKE_CURRENT_SOURCE_DIR}/cmake.dox" SORTED )

ferdymercury avatar Mar 18 '23 16:03 ferdymercury

I know nothing about doxygen, and that will show in my answer here, but if cmake had block comments, couldn't we just mark the beginning and of a bl;ock and embed doxygen comments inside? Something like this

Be cheeky, just use /** to introduce a block comment Use **/ to terminate a block comment

/**
 * @brief a function to create a depencancy and add an include directory to it
 * @param foo the target to work on
 * @param bar the dependancy
 * @param baz the include folder to add to @param(foo)
**/
function(myFunc foo bar baz)
    add_depencies(${foo} ${bar})
    target_include_dir(${foo} ${baz})
endfunction()

ga2k avatar Oct 31 '24 16:10 ga2k

It might be possible when you also use a filter as also indicated in the comment https://github.com/doxygen/doxygen/issues/9906#issuecomment-1466183889

albert-github avatar Oct 31 '24 16:10 albert-github