Bringing documentation together
Documentation is and was always my strong point and if I look back upon the year, which is about to close, it also has been a huge part inside of this blog and my daily job. During the year one critical problem (among how to motivate create a motivating environment to write documentation) remained:
How can we manage documentation that is scattered among many repositories and documentation systems?
The first problem is easily solved and I also recommended giving Antora a spin for my go-to documentation system AsciiDoc here, but what about the latter?
If you look closely, you can probably find n+1 documentation systems for every language. Examples include Javadoc for Java, Rustdoc for Rust just to name a few I daily use. Visiting all of them is totally beyond the scope of this post, so this post focuses on a more general approach with Doxygen, which also better matches my main motivation to align documentation for application and embedded software engineering.
What is Doxygen? &
Doxygen was actually the first documentation generator I’ve ever used and even my oldest C project subtle contains configuration for it.
In a nutshell Doxygen collects special comment blocks from the actual source files, takes care of all the symbols and provides various output formats like HTML in the next example:
/**
* @brief Main function (1)
*
* @details (2)
* @startuml
* main.c -> lang.c : get_lang()
* @enduml
*
* @param[in] argc Number of arguments (3)
* @param[in] argv Array with passed commandline arguments
* @retval 0 Default return value (4)
**/
int main(int argc, char *argv[]) {
printf("Hello, %s", get_lang("NL"));
return 0;
}
1 | The first section brief briefly (as the name implies) describes the method or function |
2 | A details block includes more verbose information about the implementation in the source file and can even contain Plantuml diagrams |
3 | Parameters should surprise no one besides the direction information in , out or both |
4 | And lastly return values can also be nicely laid out |
Normally Doxygen command starts with a \, but I personally prefer the Javadoc @
version via the config option JAVADOC_AUTOBRIEF .
|
Doxygen can then be run either locally or even better via container to create the first version of our output:
$ podman run --rm -v /home/unexist/projects/showcase-asciidoxy:/asciidoxy \
-it docker.io/unexist/asciidoxy-builder:0.3 \
sh -c "cd /asciidoxy && doxygen"
Doxygen version used: 1.11.0
Searching for include files...
Searching for example files...
Searching for images...
Searching for dot files...
...
Generate XML output for dir /asciidoxy/src/
Running plantuml with JAVA...
Generating PlantUML png Files in html
type lookup cache used 8/65536 hits=26 misses=8
symbol lookup cache used 16/65536 hits=50 misses=16
finished...
Once done the generated html pages look like this (in dark mode):
This works well, but unfortunately creates another documentation artifact somewhere and doesn’t move us any closer to an aggregated documentation - yet.
How can AsciiDoxy help? &
Besides the html output from above, Doxygen can also create xml files which include information about all the found symbols, their documentation and also their relationship to each other. Normally this would be quite messy to integrate into Asciidoc, but this is the gap AsciiDoxy closes as we are going to see next.
Originally created by TomTom and hopefully still managed since I’ve opened a bug on Github, it parses the xml files and ultimately provides a short list of AsciiDoc macros for convenient use inside our documents:
${language("cpp")} (1)
${insert("main", leveloffset=2)} (2)
${insert("main", template="customfunc")} (3)
1 | Set the language - the Mako templates vary a bit based on the language |
2 | Insert an actual symbol |
3 | Insert the same symbol again, but use a different template now |
The initial setup is a bit tricky, especially with the different modules, but refer to the showcase and the official manual if you are stuck. |
The container from before is equipped with the whole chain, so let us quickly fire it up:
$ podman run --rm -v /home/unexist/projects/showcase-asciidoxy:/asciidoxy \
-it docker.io/unexist/asciidoxy-builder:0.3 \
sh -c "cd /asciidoxy && asciidoxy \
--require asciidoctor-diagram \
--spec-file packages.toml \
--base-dir text \
--destination-dir src/site/asciidoc \
--build-dir build \
--template-dir templates \
-b adoc \
text/index.adoc"
___ _ _ ____ 0.8.7
/ | __________(_|_) __ \____ _ ____ __
/ /| | / ___/ ___/ / / / / / __ \| |/_/ / / /
/ ___ |(__ ) /__/ / / /_/ / /_/ /> </ /_/ /
/_/ |_/____/\___/_/_/_____/\____/_/|_|\__, /
/____/
Collecting packages : 100%|██████████████████████████████████| 1/1 [00:00<00:00, 226.55pkg/s]
Loading API reference : 100%|██████████████████████████████████| 1/1 [00:00<00:00, 47.60pkg/s]
Resolving references : 100%|██████████████████████████████████| 2/2 [00:00<00:00, 1954.48ref/s]
Checking references : 100%|██████████████████████████████████| 1/1 [00:00<00:00, 28149.69ref/s]
Preparing work directory: 100%|██████████████████████████████████| 2/2 [00:00<00:00, 267.69pkg/s]
Processing asciidoc : 100%|██████████████████████████████████| 2/2 [00:00<00:00, 67.52file/s]
Copying images : 100%|██████████████████████████████████| 2/2 [00:00<00:00, 6647.07pkg/s]
Once this step is done AsciiDoxy has expanded all the macros and replaced them with the appropriate
AsciiDoc directives like the following for ${insert("main", leveloffset=2)}
:
[#cpp-hello_8c_1a0ddf1224851353fc92bfbff6f499fa97,reftext='main']
=== main
[%autofit]
[source,cpp,subs="-specialchars,macros+"]
----
#include <src/hello.c>
int main(int argc,
char * argv)
----
main
Main function
[plantuml]
....
main.c -> lang.c : get_lang()
....
[cols='h,5a']
|===
| Parameters
|
`int argc`::
Number of arguments
`char * argv`::
Array with passed commandline arguments
| Returns
|
`int`::
|===
The markup is a bit cryptic, but shouldn’t be too hard to understand with a bit of AsciiDoc knowledge. |
AsciiDoxy can perfectly generate AsciiDoc documents by itself and even supports multipage documents, but we require an intermediate step for the next part.
Bringing everything together &
There is more than one way to generate the prepared document to its final form, but as initially told the general idea is to bring everything together.
I am not that fond of Confluence, but the goal of collecting everything in one place ranks higher than my taste here. Since rendering just the document doesn’t work here, we are going to rely on the asciidoc-confluence-publisher-maven-plugin from before.
This adds some more dependencies and finally explains why the container is based on Maven.
The base call to create the document works in the same manner as before:
$ podman run --rm --dns 8.8.8.8 -v /home/unexist/projects/showcase-asciidoxy:/asciidoxy \
-it docker.io/unexist/asciidoxy-builder:0.3 \
sh -c "cd /asciidoxy && mvn -f pom.xml generate-resources"
[INFO] Scanning for projects...
[INFO]
[INFO] --------------< dev.unexist.showcase:showcase-asciidoxy >---------------
[INFO] Building showcase-asciidoxy 0.1
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from central: https://repo.maven.apache.org/maven2/org/asciidoctor/asciidoctor-maven-plugin/2.1.0/asciidoctor-maven-plugin-2.1.0.pom
...
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO] asciidoctor: WARN: index.adoc: line 60: id assigned to section already in use: cpp-hello_8c_1a0ddf1224851353fc92bfbff6f499fa97
[INFO] Converted /asciidoxy/src/site/asciidoc/index.adoc
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 17.596 s
[INFO] Finished at: 2024-12-26T15:51:23Z
[INFO] ------------------------------------------------------------------------
And if we have a look at our final result:
Getting the actual document to Confluence is a nice exercise for my dear readers:
$ podman run --rm --dns 8.8.8.8 -v /home/unexist/projects/showcase-asciidoxy:/asciidoxy \
-it docker.io/unexist/asciidoxy-builder:$(VERSION) \
-e CONFLUENCE_URL="unexist.blog" \
-e CONFLUENCE_SPACE_KEY="UXT" \
-e CONFLUENCE_ANCESTOR_ID="123" \
-e CONFLUENCE_USER="unexist" \
-e CONFLUENCE_TOKEN="secret123" \
sh -c "cd $(MOUNTPATH) && mvn -f pom.xml -P generate-docs-and-publish generate-resources"
Give it a try, I’ll watch.
Conclusion &
Adding Doxygen and AsciiDoxy to the mix allows us to enhance our documentation with rendered meta information directly from the code and supplements the existing features of directly including code by file or tag. Being able to customize the used templates and select per symbol what is included offers great flexibility and still keeps the beautiful look of AsciiDoc.
The additional overhead of the toolchain and the intermediate steps to call Doxygen, AsciDoxy and AsciiDoc on every change is something to consider, but should be a no-brainer within a proper CICD pipeline.
All examples can be found here: