{"id":24393,"date":"2019-06-03T14:44:03","date_gmt":"2019-06-03T14:44:03","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/cppblog\/?p=24393"},"modified":"2019-06-03T14:44:03","modified_gmt":"2019-06-03T14:44:03","slug":"clear-functional-c-documentation-with-sphinx-breathe-doxygen-cmake","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/cppblog\/clear-functional-c-documentation-with-sphinx-breathe-doxygen-cmake\/","title":{"rendered":"Clear, Functional C++ Documentation with Sphinx + Breathe + Doxygen + CMake"},"content":{"rendered":"<p>Writing good documentation is hard. Tools can\u2019t solve this problem in themselves, but they can ease the pain. This post will show you how to use <a href=\"http:\/\/www.sphinx-doc.org\/en\/master\/\">Sphinx<\/a> to generate attractive, functional documentation for C++ libraries, supplied with information from <a href=\"http:\/\/www.doxygen.nl\/download.html\">Doxygen<\/a>. We\u2019ll also integrate this process into a <a href=\"https:\/\/cmake.org\/\">CMake<\/a> build system so that we have a unified workflow.<\/p>\n<p>For an example of a real-world project whose documentation is built like this, see <a href=\"http:\/\/fmtlib.net\/latest\/\">fmtlib<\/a>.<\/p>\n<h2>Why Sphinx?<\/h2>\n<p>Doxygen has been around for a couple of decades and is a stable, feature-rich tool for generating documentation. However, it is not without its issues. Docs generated with Doxygen tend to be visually noisy, have a style out of the early nineties, and struggle to clearly represent complex template-based APIs. There are also limitations to its markup. Although they added Markdown support in 2012, Markdown is simply not the best tool for writing technical documentation since it sacrifices extensibility, featureset size, and semantic markup for simplicity.<\/p>\n<p>Sphinx instead uses <a href=\"http:\/\/docutils.sourceforge.net\/rst.html\">reStructuredText<\/a>, which has those important concepts which are missing from Markdown as core ideals. One can add their own \u201croles\u201d and \u201cdirectives\u201d to the markup to make domain-specific customizations. There are some great comparisons of reStructuredText and Markdown by <a id=\"post-24393-_Hlk7532451\"><\/a><a href=\"http:\/\/www.zverovich.net\/2016\/06\/16\/rst-vs-markdown.html\">Victor <\/a><a href=\"http:\/\/www.zverovich.net\/2016\/06\/16\/rst-vs-markdown.html\">Zverovich<\/a> and <a href=\"https:\/\/eli.thegreenplace.net\/2017\/restructuredtext-vs-markdown-for-technical-documentation\/\">Eli Bendersky<\/a> if you\u2019d like some more information.<\/p>\n<p>The docs generated by Sphinx also look a lot more modern and minimal when compared to Doxygen and it\u2019s much easier to swap in a different theme, customize the amount of information which is displayed, and modify the layout of the pages.<\/p>\n<div style=\"text-align: center;\">\n<div style=\"display: inline-block;\">\n<p><figure id=\"attachment_24410\" aria-labelledby=\"figcaption_attachment_24410\" class=\"wp-caption aligncenter\" ><img decoding=\"async\" class=\"size-full\" src=\"https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/doxygen1.png\" alt=\"Doxygen's output, which has a lot of boilerplate and unused space\" width=\"187\" height=\"292\" \/><figcaption id=\"figcaption_attachment_24410\" class=\"wp-caption-text\">Doxygen output<\/figcaption><\/figure><\/p>\n<\/div>\n<div style=\"display: inline-block;\">\n<p><figure id=\"attachment_24411\" aria-labelledby=\"figcaption_attachment_24411\" class=\"wp-caption aligncenter\" ><img decoding=\"async\" class=\"size-full\" src=\"https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/doxygen1-1.png\" alt=\"Output from Sphinx, which is much more compact and attractive\" width=\"272\" height=\"190\" \/><figcaption id=\"figcaption_attachment_24411\" class=\"wp-caption-text\">Sphinx output<\/figcaption><\/figure><\/p>\n<\/div>\n<\/div>\n<p>&nbsp;<\/p>\n<p>On a more fundamental level, Doxygen\u2019s style of documentation is listing out all the API entities along with their associated comments in a more digestible, searchable manner. It\u2019s essentially paraphrasing the header files, to take a phrase from <a href=\"https:\/\/www.youtube.com\/watch?v=YxmdCxX9dMk\">Robert Ramey<\/a><sup><a id=\"post-24393-footnote-ref-2\" href=\"#post-24393-footnote-2\">[1]<\/a><\/sup>; embedding things like rationale, examples, notes, or swapping out auto-generated output for hand-written is not very well supported. In Sphinx however, the finer-grained control gives you the ability to write documentation which is truly geared towards getting people to learn and understand your library.<\/p>\n<p>If you\u2019re convinced that this is a good avenue to explore, then we can begin by installing dependencies.<\/p>\n<h2>Install Dependencies<\/h2>\n<h2>Doxygen<\/h2>\n<p>Sphinx doesn\u2019t have the ability to extract API documentation from C++ headers; this needs to be supplied either by hand or from some external tool. We can use Doxygen to do this job for us. Grab it from the <a href=\"http:\/\/www.doxygen.nl\/download.html\">official download page<\/a> and install it. There are binaries for Windows, Linux (compiled on Ubuntu 16.04), and MacOS, alongside source which you can build yourself.<\/p>\n<h2>Sphinx<\/h2>\n<p>Pick your preferred way of installing Sphinx from the <a href=\"https:\/\/www.sphinx-doc.org\/en\/master\/usage\/installation.html\">official instructions<\/a>. It may be available through your system package manager, or you can get it through <a href=\"https:\/\/pypi.org\/project\/pip\/\">pip<\/a>.<\/p>\n<h2>Read the Docs Sphinx Theme<\/h2>\n<p>I prefer this theme to the built-in ones, so we can install it through pip:<\/p>\n<pre class=\"lang:sh decode:true\">&gt; pip install sphinx_rtd_theme<\/pre>\n<h2>Breathe<\/h2>\n<p>Breathe is the bridge between Doxygen and Sphinx; taking the output from the former and making it available through some special directives in the latter. You can install it with pip:<\/p>\n<pre class=\"lang:sh decode:true\">&gt; pip install breathe<\/pre>\n<h2>CMake<\/h2>\n<p>Install the <a href=\"https:\/\/cmake.org\/download\/\">latest release of CMake<\/a>. If you are using Visual Studio 2017 and up, you will already have a version installed and ready to use. See <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/build\/cmake-projects-in-visual-studio?view=vs-2019\">CMake projects in Visual Studio<\/a> for more details.<\/p>\n<h2>Create a CMake Project<\/h2>\n<p>All of the code for this post is available <a href=\"https:\/\/github.com\/TartanLlama\/cpp-documentation-example\">on Github<\/a>, so if you get stuck, have a look there.<\/p>\n<p>If you are using Visual Studio 2017 and up, go to File &gt; New &gt; Project and create a CMake project.<\/p>\n<p><img decoding=\"async\" class=\"aligncenter size-full wp-image-24413\" src=\"https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/create_project_cmake.png\" alt=\"Create new CMake project dialogue box\" width=\"332\" height=\"228\" srcset=\"https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/create_project_cmake.png 332w, https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/create_project_cmake-300x206.png 300w\" sizes=\"(max-width: 332px) 100vw, 332px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>Regardless of which IDE\/editor you are using, get your project folder to look something like this:<\/p>\n<h4>CatCutifier\/CMakeLists.txt<\/h4>\n<pre class=\"lang:default decode:true \">cmake_minimum_required (VERSION 3.8)\r\n\r\nproject (\"CatCutifier\")\r\n\r\nadd_subdirectory (\"CatCutifier\")<\/pre>\n<h4>CatCutifier\/CatCutifier\/CatCutifier.cpp<\/h4>\n<pre class=\"lang:default decode:true\">#include \"CatCutifier.h\"\r\n\r\nvoid cat::make_cute() {\r\n  \/\/ Magic happens\r\n}<\/pre>\n<h4>CatCutifier\/CatCutifier\/CatCutifier.h<\/h4>\n<pre class=\"lang:default decode:true\">#pragma once\r\n\r\n\/**\r\n  A fluffy feline\r\n*\/\r\nstruct cat {\r\n  \/**\r\n    Make this cat look super cute\r\n  *\/\r\n  void make_cute();\r\n};<\/pre>\n<h4>CatCutifier\/CatCutifier\/CMakeLists.txt<\/h4>\n<pre class=\"lang:default decode:true\">add_library (CatCutifier \"CatCutifier.cpp\" \"CatCutifier.h\")\r\n\r\ntarget_include_directories(CatCutifier PUBLIC .)<\/pre>\n<p>If you now build your project, you should get a CatCutifier library which someone could link against and use.<\/p>\n<p>Now that we have our library, we can set up document generation.<\/p>\n<h2>Set up Doxygen<\/h2>\n<p>If you don\u2019t already have Doxygen set up for your project, you\u2019ll need to generate a configuration file so that it knows how to generate docs for your interfaces. Make sure the Doxygen executable is on your path and run:<\/p>\n<pre class=\"lang:sh decode:true\">&gt; mkdir docs\r\n&gt; cd docs\r\n&gt; doxygen.exe -g<\/pre>\n<p>You should get a message like:<\/p>\n<pre class=\"lang:default highlight:0 decode:true\">Configuration file `Doxyfile' created.\r\nNow edit the configuration file and enter\r\n  doxygen Doxyfile\r\nto generate the documentation for your project<\/pre>\n<p>We can get something generated quickly by finding the INPUT variable in the generated Doxyfile and pointing it at our code:<\/p>\n<pre class=\"lang:default decode:true\">INPUT = ..\/CatCutifier<\/pre>\n<p>Now if you run:<\/p>\n<pre class=\"lang:sh decode:true\">&gt; doxygen.exe<\/pre>\n<p>You should get an html folder generated which you can point your browser at and see some documentation like this:<\/p>\n<p><img decoding=\"async\" class=\"size-full wp-image-24410\" src=\"https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/doxygen1.png\" alt=\"Doxygen's output, which has a lot of boilerplate and unused space\" width=\"187\" height=\"292\" \/><\/p>\n<p>We\u2019ve successfully generated some simple documentation for our class by hand. But we don\u2019t want to manually run this command every time we want to rebuild the docs; this should be handled by CMake.<\/p>\n<h2>Doxygen in CMake<\/h2>\n<p>To use Doxygen from CMake, we need to find the executable. Fortunately CMake provides a <a href=\"https:\/\/cmake.org\/cmake\/help\/v3.14\/manual\/cmake-developer.7.html#find-modules\">find module<\/a> for Doxygen, so we can use find_package(Doxygen REQUIRED) to locate the binary and report an error if it doesn\u2019t exist. This will store the executable location in the DOXYGEN_EXECUTABLE variable, so we can <a href=\"https:\/\/cmake.org\/cmake\/help\/v3.14\/command\/add_custom_command.html\">add_custom_command<\/a> to run it and track dependencies properly:<\/p>\n<h4>CatCutifier\/CMakeLists.txt<\/h4>\n<pre class=\"lang:default decode:true\">cmake_minimum_required (VERSION 3.8)\r\nproject (\"CatCutifier\")\r\nadd_subdirectory (\"CatCutifier\")\r\nadd_subdirectory (\"docs\")<\/pre>\n<h4>CatCutifier\/docs\/CMakeLists.txt<\/h4>\n<pre class=\"lang:default decode:true\">find_package(Doxygen REQUIRED)\r\n\r\n# Find all the public headers\r\nget_target_property(CAT_CUTIFIER_PUBLIC_HEADER_DIR CatCutifier INTERFACE_INCLUDE_DIRECTORIES)\r\nfile(GLOB_RECURSE CAT_CUTIFIER_PUBLIC_HEADERS ${CAT_CUTIFIER_PUBLIC_HEADER_DIR}\/*.h)\r\n\r\n#This will be the main output of our command\r\nset(DOXYGEN_INDEX_FILE ${CMAKE_CURRENT_SOURCE_DIR}\/html\/index.html)\r\n\r\nadd_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE}\r\n                   DEPENDS ${CAT_CUTIFIER_PUBLIC_HEADERS}\r\n                   COMMAND ${DOXYGEN_EXECUTABLE} Doxyfile\r\n                   WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\r\n                   MAIN_DEPENDENCY Doxyfile\r\n                   COMMENT \"Generating docs\")\r\n\r\nadd_custom_target(Doxygen ALL DEPENDS ${DOXYGEN_INDEX_FILE})<\/pre>\n<p>The final custom target makes sure that we have a target name to give to make and that dependencies will be checked for a rebuild whenever we Build All or do a bare make.<\/p>\n<p>We also want to be able to control the input and output directories from CMake so that we\u2019re not flooding our source directory with output files. We can do this by adding some placeholders to our Doxyfile (we\u2019ll rename it Doxyfile.in to follow convention) and having CMake fill them in with <a href=\"https:\/\/cmake.org\/cmake\/help\/latest\/command\/configure_file.html\">configure_file<\/a>:<\/p>\n<h4>CatCutifier\/docs\/Doxyfile.in<\/h4>\n<pre class=\"lang:default decode:true \">#...\r\nINPUT = \"@DOXYGEN_INPUT_DIR@\"\r\n#...\r\nOUTPUT_DIRECTORY = \"@DOXYGEN_OUTPUT_DIR@\"\r\n#...<\/pre>\n<h4>CatCutifier\/docs\/CMakeLists.txt<\/h4>\n<pre class=\"lang:default decode:true\">find_package(Doxygen REQUIRED)\r\n\r\n# Find all the public headers\r\nget_target_property(CAT_CUTIFIER_PUBLIC_HEADER_DIR CatCutifier INTERFACE_INCLUDE_DIRECTORIES)\r\nfile(GLOB_RECURSE CAT_CUTIFIER_PUBLIC_HEADERS ${CAT_CUTIFIER_PUBLIC_HEADER_DIR}\/*.h)\r\n\r\nset(DOXYGEN_INPUT_DIR ${PROJECT_SOURCE_DIR}\/CatCutifier)\r\nset(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}\/docs\/doxygen)\r\nset(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}\/html\/index.html)\r\nset(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}\/Doxyfile.in)\r\nset(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}\/Doxyfile)\r\n\r\n#Replace variables inside @@ with the current values\r\nconfigure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY)\r\n\r\nfile(MAKE_DIRECTORY ${DOXYGEN_OUTPUT_DIR}) #Doxygen won't create this for us\r\nadd_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE}\r\n                   DEPENDS ${CAT_CUTIFIER_PUBLIC_HEADERS}\r\n                   COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT}\r\n                   MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN}\r\n                   COMMENT \"Generating docs\")\r\n\r\nadd_custom_target(Doxygen ALL DEPENDS ${DOXYGEN_INDEX_FILE})<\/pre>\n<p>Now we can generate our documentation as part of our build system and it\u2019ll only be generated when it needs to be. If you\u2019re happy with Doxygen\u2019s output, you could just stop here, but if you want the additional features and attractive output which reStructuredText and Sphinx give you, then read on.<\/p>\n<h2>Setting up Sphinx<\/h2>\n<p>Sphinx provides a nice startup script to get us going fast. Go ahead and run this:<\/p>\n<pre class=\"lang:sh decode:true\">&gt; cd docs\r\n&gt; sphinx-quickstart.exe<\/pre>\n<p>Keep the defaults and put in your name and the name of your project. Now if you run make html you should get a _build\/html folder you can point your browser at to see a welcome screen.<\/p>\n<p><img decoding=\"async\" class=\"aligncenter size-full wp-image-24420\" src=\"https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/cat_cutifier.png\" alt=\"Front page saying &quot;Welcome to CatCutifier's documentation with links to the Index, Module Index and Search Page\" width=\"553\" height=\"192\" srcset=\"https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/cat_cutifier.png 553w, https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/cat_cutifier-300x104.png 300w\" sizes=\"(max-width: 553px) 100vw, 553px\" \/><\/p>\n<p>I\u2019m a fan of the Read the Docs theme we installed at the start, so we can use that instead by changing html_theme in conf.py to be \u2018sphinx_rtd_theme\u2019. That gives us this look:<\/p>\n<p><img decoding=\"async\" class=\"aligncenter size-full wp-image-24421\" src=\"https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/better_cat_cutifier.png\" alt=\"The same content as above, but the visual design is more attractive\" width=\"587\" height=\"174\" srcset=\"https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/better_cat_cutifier.png 587w, https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/better_cat_cutifier-300x89.png 300w\" sizes=\"(max-width: 587px) 100vw, 587px\" \/><\/p>\n<p>Before we link in the Doxygen output to give us the documentation we desire, lets automate the Sphinx build with CMake<\/p>\n<h2>Sphinx in CMake<\/h2>\n<p>Ideally we want to be able to write find_package(Sphinx REQUIRED) and have everything work. Unfortunately, unlike Doxygen, Sphinx doesn\u2019t have a find module provided by default, so we\u2019ll need to write one. Fortunately, we can get away with doing very little work:<\/p>\n<h4>CatCutifier\/cmake\/FindSphinx.cmake<\/h4>\n<pre class=\"lang:default decode:true\">#Look for an executable called sphinx-build\r\nfind_program(SPHINX_EXECUTABLE\r\n             NAMES sphinx-build\r\n             DOC \"Path to sphinx-build executable\")\r\n\r\ninclude(FindPackageHandleStandardArgs)\r\n\r\n#Handle standard arguments to find_package like REQUIRED and QUIET\r\nfind_package_handle_standard_args(Sphinx\r\n                                  \"Failed to find sphinx-build executable\"\r\n                                  SPHINX_EXECUTABLE)<\/pre>\n<p>With this file in place, find_package will work so long as we tell CMake to look for find modules in that directory:<\/p>\n<h4>CatCutifier\/CMakeLists.txt<\/h4>\n<pre class=\"lang:default decode:true\">cmake_minimum_required (VERSION 3.8)\r\n\r\nproject (\"CatCutifier\")\r\n\r\n# Add the cmake folder so the FindSphinx module is found\r\nset(CMAKE_MODULE_PATH \"${PROJECT_SOURCE_DIR}\/cmake\" ${CMAKE_MODULE_PATH})\r\n\r\nadd_subdirectory (\"CatCutifier\")\r\nadd_subdirectory (\"docs\")<\/pre>\n<p>Now we can find this executable and call it:<\/p>\n<h4>CatCutifier\/docs\/CMakeLists.txt<\/h4>\n<pre class=\"lang:default decode:true\">find_package(Sphinx REQUIRED)\r\n\r\nset(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR})\r\nset(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}\/docs\/sphinx)\r\n\r\nadd_custom_target(Sphinx ALL\r\n                  COMMAND\r\n                  ${SPHINX_EXECUTABLE} -b html\r\n                  ${SPHINX_SOURCE} ${SPHINX_BUILD}\r\n                  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}\r\n                  COMMENT \"Generating documentation with Sphinx\")<\/pre>\n<p>If you run a build you should now see Sphinx running and generating the same blank docs we saw earlier.<\/p>\n<p>Now we have the basics set up, we need to hook Sphinx up with the information generated by Doxygen. We do that using Breathe.<\/p>\n<h2>Setting up Breathe<\/h2>\n<p>Breathe is an extension to Sphinx, so we set it up using the conf.py which was generated for us in the last step:<\/p>\n<h4>CatCutifier\/docs\/conf.py<\/h4>\n<pre class=\"lang:default decode:true\">#...\r\nextensions = [ \"breathe\" ]\r\n#...\r\n\r\n# Breathe Configuration\r\nbreathe_default_project = \"CatCutifier\"<\/pre>\n<p>Breathe uses Doxygen\u2019s XML output, which is disabled by default, so we need to turn it on:<\/p>\n<h4>CatCutifier\/docs\/Doxyfile.in<\/h4>\n<pre class=\"lang:default decode:true\">#...\r\nGENERATE_XML = YES\r\n#...<\/pre>\n<p>We\u2019ll need to put placeholders in our docs to tell Sphinx where to put our API information. We achieve this with <a href=\"https:\/\/breathe.readthedocs.io\/en\/latest\/directives.html\">directives supplied by Breathe<\/a>, such as <a href=\"https:\/\/breathe.readthedocs.io\/en\/latest\/directives.html#doxygenstruct\">doxygenstruct<\/a>:<\/p>\n<h4>CatCutifier\/docs\/index.rst<\/h4>\n<pre class=\"lang:default decode:true\">\u2026\r\n\r\nDocs\r\n====\r\n\r\n.. doxygenstruct:: cat\r\n   :members:<\/pre>\n<p>You might wonder why it\u2019s necessary to explicitly state what entities we wish to document and where, but this is one of the key benefits of Sphinx. This allows us to add as much additional information (examples, rationale, notes, etc.) as we want to the documentation without having to shoehorn it into the source code, plus we can make sure it\u2019s displayed in the most accessible, understandable manner we can. Have a look through <a href=\"https:\/\/breathe.readthedocs.io\/en\/latest\/directives.html\">Breathe\u2019s directives<\/a> and <a href=\"https:\/\/www.sphinx-doc.org\/en\/master\/usage\/restructuredtext\/directives.html\">Sphinx\u2019s built-in directives<\/a>, and <a href=\"https:\/\/www.sphinx-doc.org\/en\/master\/usage\/restructuredtext\/domains.html#cpp-domain\">Sphinx\u2019s C++-specific directives<\/a> to get a feel for what\u2019s available.<\/p>\n<p>Now we update our Sphinx target to hook it all together by telling Breathe where to find the Doxygen output:<\/p>\n<h4>CatCutifier\/docs\/CMakeLists.txt<\/h4>\n<pre class=\"lang:default decode:true\">#...\r\n\r\nfind_package(Sphinx REQUIRED)\r\n\r\nset(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR})\r\nset(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}\/docs\/sphinx)\r\n\r\nadd_custom_target(Sphinx ALL\r\n                  COMMAND ${SPHINX_EXECUTABLE} -b html\r\n                  # Tell Breathe where to find the Doxygen output\r\n                  -Dbreathe_projects.CatCutifier=${DOXYGEN_OUTPUT_DIR}\r\n                  ${SPHINX_SOURCE} ${SPHINX_BUILD}\r\n                  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}\r\n                  COMMENT \"Generating documentation with Sphinx\")<\/pre>\n<p>Hooray! You should now have some nice Sphinx documentation generated for you:<\/p>\n<p><img decoding=\"async\" class=\"size-full wp-image-24411\" src=\"https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/doxygen1-1.png\" alt=\"Output from Sphinx, which is much more compact and attractive\" width=\"272\" height=\"190\" \/><\/p>\n<p>Finally, we can make sure all of our dependencies are right so that we never rebuild the Doxygen files or the Sphinx docs when we don\u2019t need to:<\/p>\n<h4>CatCutifier\/docs\/CMakeLists.txt<\/h4>\n<pre class=\"lang:default decode:true\">find_package(Doxygen REQUIRED)\r\nfind_package(Sphinx REQUIRED)\r\n\r\n# Find all the public headers\r\nget_target_property(CAT_CUTIFIER_PUBLIC_HEADER_DIR CatCutifier INTERFACE_INCLUDE_DIRECTORIES)\r\nfile(GLOB_RECURSE CAT_CUTIFIER_PUBLIC_HEADERS ${CAT_CUTIFIER_PUBLIC_HEADER_DIR}\/*.h)\r\n\r\nset(DOXYGEN_INPUT_DIR ${PROJECT_SOURCE_DIR}\/CatCutifier)\r\nset(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}\/doxygen)\r\nset(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}\/xml\/index.xml)\r\nset(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}\/Doxyfile.in)\r\nset(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}\/Doxyfile)\r\n\r\n# Replace variables inside @@ with the current values\r\nconfigure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY)\r\n\r\n# Doxygen won't create this for us\r\nfile(MAKE_DIRECTORY ${DOXYGEN_OUTPUT_DIR})\r\n\r\n# Only regenerate Doxygen when the Doxyfile or public headers change\r\nadd_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE}\r\n                   DEPENDS ${CAT_CUTIFIER_PUBLIC_HEADERS}\r\n                   COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT}\r\n                   MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN}\r\n                   COMMENT \"Generating docs\"\r\n                   VERBATIM)\r\n\r\n# Nice named target so we can run the job easily\r\nadd_custom_target(Doxygen ALL DEPENDS ${DOXYGEN_INDEX_FILE})\r\n\r\nset(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR})\r\nset(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}\/sphinx)\r\nset(SPHINX_INDEX_FILE ${SPHINX_BUILD}\/index.html)\r\n\r\n# Only regenerate Sphinx when:\r\n# - Doxygen has rerun\r\n# - Our doc files have been updated\r\n# - The Sphinx config has been updated\r\nadd_custom_command(OUTPUT ${SPHINX_INDEX_FILE}\r\n                   COMMAND \r\n                     ${SPHINX_EXECUTABLE} -b html\r\n                     # Tell Breathe where to find the Doxygen output\r\n                     -Dbreathe_projects.CatCutifier=${DOXYGEN_OUTPUT_DIR}\/xml\r\n                   ${SPHINX_SOURCE} ${SPHINX_BUILD}\r\n                   WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}\r\n                   DEPENDS\r\n                   # Other docs files you want to track should go here (or in some variable)\r\n                   ${CMAKE_CURRENT_SOURCE_DIR}\/index.rst\r\n                   ${DOXYGEN_INDEX_FILE}\r\n                   MAIN_DEPENDENCY ${SPHINX_SOURCE}\/conf.py\r\n                   COMMENT \"Generating documentation with Sphinx\")\r\n\r\n# Nice named target so we can run the job easily\r\nadd_custom_target(Sphinx ALL DEPENDS ${SPHINX_INDEX_FILE})\r\n\r\n# Add an install target to install the docs\r\ninclude(GNUInstallDirs)\r\ninstall(DIRECTORY ${SPHINX_BUILD}\r\nDESTINATION ${CMAKE_INSTALL_DOCDIR})<\/pre>\n<p>Try it out and see what gets rebuilt when you change a file. If you change Doxyfile.in or a header file, all the docs should get rebuilt, but if you only change the Sphinx config or reStructuredText files then the Doxygen build should get skipped.<\/p>\n<p>This leaves us with an efficient, automated, powerful documentation system.<\/p>\n<p>If you already have somewhere to host the docs or want developers to build the docs themselves then we\u2019re finished. If not, you can host them on <a href=\"https:\/\/readthedocs.org\/\">Read the Docs<\/a>, which provides free hosting for open source projects.<\/p>\n<h2>Setting up <em>Read the Docs<\/em><\/h2>\n<p>To use Read the Docs (RtD) you need to <a href=\"https:\/\/readthedocs.org\/accounts\/signup\/\">sign up<\/a> (you can use GitHub, GitLab or Bitbucket to make integration easy). Log in, import your repository, and your docs will begin to build!<\/p>\n<p>Unfortunately, it will also fail:<\/p>\n<pre class=\"lang:default highlight:0 decode:true\">Traceback (most recent call last): File \"\/home\/docs\/checkouts\/readthedocs.org\/user_builds\/cpp-documentation-example\/envs\/latest\/lib\/python3.7\/site-packages\/sphinx\/registry.py\", line 472, in load_extension mod = __import__(extname, None, None, ['setup']) ModuleNotFoundError: No module named 'breathe'<\/pre>\n<p>To tell RtD to install Breathe before building, we can add a requirements file:<\/p>\n<h4>CatCutifier\/docs\/requirements.txt<\/h4>\n<pre class=\"lang:default decode:true \">breathe<\/pre>\n<p>Another issue is that RtD doesn\u2019t understand CMake: it\u2019s finding the Sphinx config file and running that, so it won\u2019t generate the Doxygen information. To generate this, we can add some lines to our conf.py script to check if we\u2019re running in on the RtD servers and, if so, hardcode some paths and run Doxygen:<\/p>\n<h4>CatCutifier\/docs\/conf.py<\/h4>\n<pre class=\"lang:python decode:true \">import subprocess, os\r\n\r\ndef configureDoxyfile(input_dir, output_dir):\r\n    with open('Doxyfile.in', 'r') as file :\r\n        filedata = file.read()\r\n\r\n    filedata = filedata.replace('@DOXYGEN_INPUT_DIR@', input_dir)\r\n    filedata = filedata.replace('@DOXYGEN_OUTPUT_DIR@', output_dir)\r\n\r\n    with open('Doxyfile', 'w') as file:\r\n        file.write(filedata)\r\n\r\n# Check if we're running on Read the Docs' servers\r\nread_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True'\r\n\r\nbreathe_projects = {}\r\n\r\nif read_the_docs_build:\r\n    input_dir = '..\/CatCutifier'\r\n    output_dir = 'build'\r\n    configureDoxyfile(input_dir, output_dir)\r\n    subprocess.call('doxygen', shell=True)\r\n    breathe_projects['CatCutifier'] = output_dir + '\/xml'\r\n\r\n# ...<\/pre>\n<p>Push this change and\u2026<\/p>\n<p><img decoding=\"async\" class=\"aligncenter size-full wp-image-24422\" src=\"https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/read_the_docs.png\" alt=\"Full documentation page built on read the docs\" width=\"673\" height=\"533\" srcset=\"https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/read_the_docs.png 673w, https:\/\/devblogs.microsoft.com\/cppblog\/wp-content\/uploads\/sites\/9\/2019\/05\/read_the_docs-300x238.png 300w\" sizes=\"(max-width: 673px) 100vw, 673px\" \/><\/p>\n<p>Lovely documentation built automatically on every commit.<\/p>\n<h2>Conclusion<\/h2>\n<p>All this tooling takes a fair amount of effort to set up, but the result is powerful, expressive, and accessible. None of this is a substitute for clear writing and a strong grasp of what information a user of a library needs to use it effectively, but our new system can provide support to make this easier for developers.<\/p>\n<h2>Resources<\/h2>\n<p>Thank you to the authors and presenters of these resources, which were very helpful in putting together this post and process:<\/p>\n<p><a href=\"https:\/\/vicrucann.github.io\/tutorials\/quick-cmake-doxygen\/\">https:\/\/vicrucann.github.io\/tutorials\/quick-cmake-doxygen\/<\/a><\/p>\n<p><a href=\"https:\/\/eb2.co\/blog\/2012\/03\/sphinx-and-cmake-beautiful-documentation-for-c---projects\/\">https:\/\/eb2.co\/blog\/2012\/03\/sphinx-and-cmake-beautiful-documentation-for-c&#8212;projects\/<\/a><\/p>\n<p><a href=\"https:\/\/nazavode.github.io\/blog\/cmake-doxygen-improved\/\">https:\/\/nazavode.github.io\/blog\/cmake-doxygen-improved\/<\/a><\/p>\n<p><a href=\"http:\/\/www.zverovich.net\/2016\/06\/16\/rst-vs-markdown.html\">http:\/\/www.zverovich.net\/2016\/06\/16\/rst-vs-markdown.html<\/a><\/p>\n<p><a href=\"https:\/\/eli.thegreenplace.net\/2017\/restructuredtext-vs-markdown-for-technical-documentation\/\">https:\/\/eli.thegreenplace.net\/2017\/restructuredtext-vs-markdown-for-technical-documentation\/<\/a><\/p>\n<p><a href=\"https:\/\/www.youtube.com\/watch?v=YxmdCxX9dMk\">https:\/\/www.youtube.com\/watch?v=YxmdCxX9dMk<\/a><\/p>\n<ol>\n<li id=\"post-24393-footnote-2\">I would highly recommend watching this talk to help you think about what you put in your documentation. <a href=\"#post-24393-footnote-ref-2\">\u2191<\/a><\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Writing good documentation is hard. Tools can\u2019t solve this problem in themselves, but they can ease the pain. This post will show you how to use Sphinx to generate attractive, functional documentation for C++ libraries, supplied with information from Doxygen. We\u2019ll also integrate this process into a CMake build system so that we have a [&hellip;]<\/p>\n","protected":false},"author":706,"featured_media":24411,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[512],"tags":[],"class_list":["post-24393","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-general-cpp-series"],"acf":[],"blog_post_summary":"<p>Writing good documentation is hard. Tools can\u2019t solve this problem in themselves, but they can ease the pain. This post will show you how to use Sphinx to generate attractive, functional documentation for C++ libraries, supplied with information from Doxygen. We\u2019ll also integrate this process into a CMake build system so that we have a [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts\/24393","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/users\/706"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/comments?post=24393"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts\/24393\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/media\/24411"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/media?parent=24393"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/categories?post=24393"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/tags?post=24393"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}