Просмотр исходного кода

Improve Doxygen support

Except Graphviz's `dot` Doxygen may use few other utilities like
`mscgen` (Message Sequence Chart) and `dia` (Diagram Editor).

Now this module allows to manage Doxygen settings from `CMakeLists.txt`
and forget about `Doxyfile`s. Also it provides a helper function
to add a target to generate documentation: `doxygen_add_docs`.

Implement code review notes:

- Introduce `COMPONENTS` to find: `dot`, `mscgen` and `dia`;
- Deprecate variables `DOXYGEN_SKIP_DOT`, `DOXYGEN_EXECUTABLE`,
  `DOXYGEN_DOT_EXECUTABLE`, `DOXYGEN_DOT_FOUND` in favour of
  `doxygen_add_docs ` usage instead;
- Properly handle paths to found tools in Windows;
- Prevent adding a custom target if Doxygen was not really found;
- Introduce exported (executable) targets for found components.

Co-Author: Craig Scott <[email protected]>
Alex Turbov 8 лет назад
Родитель
Сommit
59ffabfeec

+ 10 - 0
Help/release/dev/doxygen-improvements.rst

@@ -0,0 +1,10 @@
+doxygen-improvements
+--------------------
+
+* The :module:`FindDoxygen` module learned to control Doxygen behavior using
+  CMake variables and generate documentation via the newly added
+  :command:`doxygen_add_docs` function. The Doxygen input file (``Doxyfile``)
+  is automatically generated and doxygen is run as part of a custom target.
+  A number of doxygen-related variables have been deprecated. Additional
+  components can be specified to find optional tools: ``dot``, ``mscgen``
+  and ``dia``.

+ 997 - 124
Modules/FindDoxygen.cmake

@@ -1,161 +1,1034 @@
 # Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
 # file Copyright.txt or https://cmake.org/licensing for details.
 
-#.rst:
-# FindDoxygen
-# -----------
-#
-# This module looks for Doxygen and the path to Graphviz's dot
-#
-# Doxygen is a documentation generation tool.  Please see
-# http://www.doxygen.org
-#
-# This module accepts the following optional variables:
-#
-# ::
-#
-#    DOXYGEN_SKIP_DOT       = If true this module will skip trying to find Dot
-#                             (an optional component often used by Doxygen)
-#
-#
-#
-# This modules defines the following variables:
-#
-# ::
-#
-#    DOXYGEN_EXECUTABLE     = The path to the doxygen command.
-#    DOXYGEN_FOUND          = Was Doxygen found or not?
-#    DOXYGEN_VERSION        = The version reported by doxygen --version
-#
-#
-#
-# ::
-#
-#    DOXYGEN_DOT_EXECUTABLE = The path to the dot program used by doxygen.
-#    DOXYGEN_DOT_FOUND      = Was Dot found or not?
-#
-# For compatibility with older versions of CMake, the now-deprecated
-# variable ``DOXYGEN_DOT_PATH`` is set to the path to the directory
-# containing ``dot`` as reported in ``DOXYGEN_DOT_EXECUTABLE``.
-# The path may have forward slashes even on Windows and is not
-# suitable for direct substitution into a ``Doxyfile.in`` template.
-# If you need this value, use :command:`get_filename_component`
-# to compute it from ``DOXYGEN_DOT_EXECUTABLE`` directly, and
-# perhaps the :command:`file(TO_NATIVE_PATH)` command to prepare
-# the path for a Doxygen configuration file.
+#[=======================================================================[.rst:
+FindDoxygen
+-----------
+
+Doxygen is a documentation generation tool (see http://www.doxygen.org).
+This module looks for Doxygen and some optional tools it supports. These
+tools are enabled as components in the :command:`find_package` command:
+
+``dot``
+  `Graphviz <http://graphviz.org>`_ ``dot`` utility used to render various
+  graphs.
+``mscgen``
+  `Message Chart Generator <http://www.mcternan.me.uk/mscgen/>`_ utility used
+  by Doxygen's ``\msc`` and ``\mscfile`` commands.
+``dia``
+  `Dia <https://wiki.gnome.org/Apps/Dia>`_ the diagram editor used by Doxygen's
+  ``\diafile`` command.
+
+Examples:
+
+.. code-block:: cmake
+
+  # Require dot, treat the other components as optional
+  find_package(Doxygen
+               REQUIRED dot
+               OPTIONAL_COMPONENTS mscgen dia)
+
+The following variables are defined by this module:
+
+.. variable:: DOXYGEN_FOUND
+
+  True if the ``doxygen`` executable was found.
+
+.. variable:: DOXYGEN_VERSION
+
+  The version reported by ``doxygen --version``.
+
+The module defines ``IMPORTED`` targets for Doxygen and each component found.
+These can be used as part of custom commands, etc. and should be preferred over
+old-style (and now deprecated) variables like ``DOXYGEN_EXECUTABLE``. The
+following import targets are defined if their corresponding executable could be
+found (the component import targets will only be defined if that component was
+requested):
+
+::
+
+  Doxygen::doxygen
+  Doxygen::dot
+  Doxygen::mscgen
+  Doxygen::dia
+
+
+Functions
+^^^^^^^^^
+
+.. command:: doxygen_add_docs
+
+  This function is intended as a convenience for adding a target for generating
+  documentation with Doxygen. It aims to provide sensible defaults so that
+  projects can generally just provide the input files and directories and that
+  will be sufficient to give sensible results. The function supports the
+  ability to customize the Doxygen configuration used to build the
+  documentation.
+
+  ::
+
+    doxygen_add_docs(targetName
+        [filesOrDirs...]
+        [WORKING_DIRECTORY dir]
+        [COMMENT comment])
+
+  The function constructs a ``Doxyfile`` and defines a custom target that runs
+  Doxygen on that generated file. The listed files and directories are used as
+  the ``INPUT`` of the generated ``Doxyfile`` and they can contain wildcards.
+  Any files that are listed explicitly will also be added as ``SOURCES`` of the
+  custom target so they will show up in an IDE project's source list.
+
+  So that relative input paths work as expected, by default the working
+  directory of the Doxygen command will be the current source directory (i.e.
+  :variable:`CMAKE_CURRENT_SOURCE_DIR`). This can be overridden with the
+  ``WORKING_DIRECTORY`` option to change the directory used as the relative
+  base point. Note also that Doxygen's default behavior is to strip the working
+  directory from relative paths in the generated documentation (see the
+  ``STRIP_FROM_PATH`` `Doxygen config option
+  <http://www.doxygen.org/manual/config.html>`_ for details).
+
+  If provided, the optional ``comment`` will be passed as the ``COMMENT`` for
+  the :command:`add_custom_target` command used to create the custom target
+  internally.
+
+  The contents of the generated ``Doxyfile`` can be customized by setting CMake
+  variables before calling ``doxygen_add_docs()``. Any variable with a name of
+  the form ``DOXYGEN_<tag>`` will have its value substituted for the
+  corresponding ``<tag>`` configuration option in the ``Doxyfile``. See the
+  `Doxygen documentation <http://www.doxygen.org/manual/config.html>`_ for the
+  full list of supported configuration options.
+
+  Some of Doxygen's defaults are overridden to provide more appropriate
+  behavior for a CMake project. Each of the following will be explicitly set
+  unless the variable already has a value before ``doxygen_add_docs()`` is
+  called (with some exceptions noted):
+
+  .. variable:: DOXYGEN_HAVE_DOT
+
+    Set to ``YES`` if the ``dot`` component was requested and it was found,
+    ``NO`` otherwise. Any existing value of ``DOXYGEN_HAVE_DOT`` is ignored.
+
+  .. variable:: DOXYGEN_DOT_MULTI_TARGETS
+
+    Set to ``YES`` by this module (note that this requires a ``dot`` version
+    newer than 1.8.10). This option is only meaningful if ``DOXYGEN_HAVE_DOT``
+    is also set to ``YES``.
+
+  .. variable:: DOXYGEN_GENERATE_LATEX
+
+    Set to ``NO`` by this module.
+
+  .. variable:: DOXYGEN_WARN_FORMAT
+
+    For Visual Studio based generators, this is set to the form recognized by
+    the Visual Studio IDE: ``$file($line) : $text``. For all other generators,
+    Doxygen's default value is not overridden.
+
+  .. variable:: DOXYGEN_PROJECT_NAME
+
+    Populated with the name of the current project (i.e.
+    :variable:`PROJECT_NAME`).
+
+  .. variable:: DOXYGEN_PROJECT_NUMBER
+
+    Populated with the version of the current project (i.e.
+    :variable:`PROJECT_VERSION`).
+
+  .. variable:: DOXYGEN_PROJECT_BRIEF
+
+    Populated with the description of the current project (i.e.
+    :variable:`PROJECT_DESCRIPTION`).
+
+  .. variable:: DOXYGEN_INPUT
+
+    Projects should not set this variable. It will be populated with the set of
+    files and directories passed to ``doxygen_add_docs()``, thereby providing
+    consistent behavior with the other built-in commands like
+    :command:`add_executable`, :command:`add_library` and
+    :command:`add_custom_target`. If a variable named ``DOXYGEN_INPUT`` is set
+    by the project, it will be ignored and a warning will be issued.
+
+  .. variable:: DOXYGEN_RECURSIVE
+
+    Set to ``YES`` by this module.
+
+  .. variable:: DOXYGEN_EXCLUDE_PATTERNS
+
+    If the set of inputs includes directories, this variable will specify
+    patterns used to exclude files from them. The following patterns are added
+    by ``doxygen_add_docs()`` to ensure CMake-specific files and directories
+    are not included in the input. If the project sets
+    ``DOXYGEN_EXCLUDE_PATTERNS``, those contents are merged with these
+    additional patterns rather than replacing them:
+
+    ::
+
+      */.git/*
+      */.svn/*
+      */.hg/*
+      */CMakeFiles/*
+      */_CPack_Packages/*
+      DartConfiguration.tcl
+      CMakeLists.txt
+      CMakeCache.txt
+
+  .. variable:: DOXYGEN_OUTPUT_DIRECTORY
+
+    Set to :variable:`CMAKE_CURRENT_BINARY_DIR` by this module. Note that if
+    the project provides its own value for this and it is a relative path, it
+    will be converted to an absolute path relative to the current binary
+    directory. This is necessary because doxygen will normally be run from a
+    directory within the source tree so that relative source paths work as
+    expected.
+
+To change any of these defaults or override any other Doxygen config option,
+set relevant variables before calling ``doxygen_add_docs()``. For example:
+
+  .. code-block:: cmake
+
+    set(DOXYGEN_GENERATE_HTML NO)
+    set(DOXYGEN_GENERATE_MAN YES)
+
+    doxygen_add_docs(
+        doxygen
+        ${PROJECT_SOURCE_DIR}
+        COMMENT "Generate man pages"
+    )
+
+A number of Doxygen config options accept lists of values, but Doxygen requires
+them to be separated by whitespace. CMake variables hold lists as a string with
+items separated by semi-colons, so a conversion needs to be performed. The
+``doxygen_add_docs()`` command specifically checks the following Doxygen config
+options and will convert their associated CMake variable's contents into the
+required form if set.
+
+::
+
+  ABBREVIATE_BRIEF
+  ALIASES
+  CITE_BIB_FILES
+  DIAFILE_DIRS
+  DOTFILE_DIRS
+  DOT_FONTPATH
+  ENABLED_SECTIONS
+  EXAMPLE_PATH
+  EXAMPLE_PATTERNS
+  EXCLUDE
+  EXCLUDE_PATTERNS
+  EXCLUDE_SYMBOLS
+  EXPAND_AS_DEFINED
+  EXTENSION_MAPPING
+  EXTRA_PACKAGES
+  EXTRA_SEARCH_MAPPINGS
+  FILE_PATTERNS
+  FILTER_PATTERNS
+  FILTER_SOURCE_PATTERNS
+  HTML_EXTRA_FILES
+  HTML_EXTRA_STYLESHEET
+  IGNORE_PREFIX
+  IMAGE_PATH
+  INCLUDE_FILE_PATTERNS
+  INCLUDE_PATH
+  INPUT
+  LATEX_EXTRA_FILES
+  LATEX_EXTRA_STYLESHEET
+  MATHJAX_EXTENSIONS
+  MSCFILE_DIRS
+  PLANTUML_INCLUDE_PATH
+  PREDEFINED
+  QHP_CUST_FILTER_ATTRS
+  QHP_SECT_FILTER_ATTRS
+  STRIP_FROM_INC_PATH
+  STRIP_FROM_PATH
+  TAGFILES
+  TCL_SUBST
+
+The following single value Doxygen options would be quoted automatically
+if they contain at least one space:
+
+::
+
+  CHM_FILE
+  DIA_PATH
+  DOCBOOK_OUTPUT
+  DOCSET_FEEDNAME
+  DOCSET_PUBLISHER_NAME
+  DOT_FONTNAME
+  DOT_PATH
+  EXTERNAL_SEARCH_ID
+  FILE_VERSION_FILTER
+  GENERATE_TAGFILE
+  HHC_LOCATION
+  HTML_FOOTER
+  HTML_HEADER
+  HTML_OUTPUT
+  HTML_STYLESHEET
+  INPUT_FILTER
+  LATEX_FOOTER
+  LATEX_HEADER
+  LATEX_OUTPUT
+  LAYOUT_FILE
+  MAN_OUTPUT
+  MAN_SUBDIR
+  MATHJAX_CODEFILE
+  MSCGEN_PATH
+  OUTPUT_DIRECTORY
+  PERL_PATH
+  PLANTUML_JAR_PATH
+  PROJECT_BRIEF
+  PROJECT_LOGO
+  PROJECT_NAME
+  QCH_FILE
+  QHG_LOCATION
+  QHP_CUST_FILTER_NAME
+  QHP_VIRTUAL_FOLDER
+  RTF_EXTENSIONS_FILE
+  RTF_OUTPUT
+  RTF_STYLESHEET_FILE
+  SEARCHDATA_FILE
+  USE_MDFILE_AS_MAINPAGE
+  WARN_FORMAT
+  WARN_LOGFILE
+  XML_OUTPUT
+
+Deprecated Result Variables
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For compatibility with previous versions of CMake, the following variables
+are also defined but they are deprecated and should no longer be used:
+
+.. variable:: DOXYGEN_EXECUTABLE
+
+  The path to the ``doxygen`` command. If projects need to refer to the
+  ``doxygen`` executable directly, they should use the ``Doxygen::doxygen``
+  import target instead.
+
+.. variable:: DOXYGEN_DOT_FOUND
+
+  True if the ``dot`` executable was found.
+
+.. variable:: DOXYGEN_DOT_EXECUTABLE
+
+  The path to the ``dot`` command. If projects need to refer to the ``dot``
+  executable directly, they should use the ``Doxygen::dot`` import target
+  instead.
+
+.. variable:: DOXYGEN_DOT_PATH
+
+  The path to the directory containing the ``dot`` executable as reported in
+  ``DOXYGEN_DOT_EXECUTABLE``. The path may have forward slashes even on Windows
+  and is not suitable for direct substitution into a ``Doxyfile.in`` template.
+  If you need this value, get the :prop_tgt:`IMPORTED_LOCATION` property of the
+  ``Doxygen::dot`` target and use :command:`get_filename_component` to extract
+  the directory part of that path. You may also want to consider using
+  :command:`file(TO_NATIVE_PATH)` to prepare the path for a Doxygen
+  configuration file.
+
+
+Deprecated Hint Variables
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. variable:: DOXYGEN_SKIP_DOT
+
+  This variable has no any effect for component form of ``find_package``.
+  In backward compatibility mode (i.e. without components list) it prevents
+  the finder module from searching for Graphviz's ``dot`` utility.
+
+#]=======================================================================]
 
 # For backwards compatibility support
 if(Doxygen_FIND_QUIETLY)
-  set(DOXYGEN_FIND_QUIETLY TRUE)
+    set(DOXYGEN_FIND_QUIETLY TRUE)
 endif()
 
 # ===== Rationale for OS X AppBundle mods below =====
-#     With the OS X GUI version, Doxygen likes to be installed to /Applications and
-#     it contains the doxygen executable in the bundle. In the versions I've
-#     seen, it is located in Resources, but in general, more often binaries are
-#     located in MacOS.
-#
-#     NOTE: The official Doxygen.app that is distributed for OS X uses non-standard
-#     conventions.  Instead of the command-line "doxygen" tool being placed in
-#     Doxygen.app/Contents/MacOS, "Doxywizard" is placed there instead and
-#     "doxygen" is placed in Contents/Resources.  This is most likely done
-#     so that something happens when people double-click on the Doxygen.app
-#     package.  Unfortunately, CMake gets confused by this as when it sees the
-#     bundle it uses "Doxywizard" as the executable to use instead of
-#     "doxygen".  Therefore to work-around this issue we temporarily disable
-#     the app-bundle feature, just for this CMake module:
+#  With the OS X GUI version, Doxygen likes to be installed to /Applications
+#  and it contains the doxygen executable in the bundle. In the versions I've
+#  seen, it is located in Resources, but in general, more often binaries are
+#  located in MacOS.
+#
+#  NOTE: The official Doxygen.app distributed for OS X uses non-standard
+#  conventions. Instead of the command-line "doxygen" tool being placed in
+#  Doxygen.app/Contents/MacOS, "Doxywizard" is placed there instead and
+#  "doxygen" is placed in Contents/Resources.  This is most likely done
+#  so that something happens when people double-click on the Doxygen.app
+#  package. Unfortunately, CMake gets confused by this as when it sees the
+#  bundle it uses "Doxywizard" as the executable to use instead of
+#  "doxygen". Therefore to work-around this issue we temporarily disable
+#  the app-bundle feature, just for this CMake module:
+#
 if(APPLE)
-    #  Save the old setting
+    # Save the old setting
     set(TEMP_DOXYGEN_SAVE_CMAKE_FIND_APPBUNDLE ${CMAKE_FIND_APPBUNDLE})
     # Disable the App-bundle detection feature
     set(CMAKE_FIND_APPBUNDLE "NEVER")
 endif()
-#     FYI:
-#     In the older versions of OS X Doxygen, dot was included with the
-#     Doxygen bundle. But the new versions require you to download
-#     Graphviz.app which contains "dot" in it's bundle.
+# FYI:
+# In older versions of OS X Doxygen, dot was included with the Doxygen bundle,
+# but newer versions require you to download Graphviz.app which contains "dot"
+# or use something like homebrew.
 # ============== End OSX stuff ================
 
 #
 # Find Doxygen...
 #
+macro(_Doxygen_find_doxygen)
+    find_program(
+        DOXYGEN_EXECUTABLE
+        NAMES doxygen
+        PATHS
+            "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\doxygen_is1;Inno Setup: App Path]/bin"
+            /Applications/Doxygen.app/Contents/Resources
+            /Applications/Doxygen.app/Contents/MacOS
+        DOC "Doxygen documentation generation tool (http://www.doxygen.org)"
+    )
+    mark_as_advanced(DOXYGEN_EXECUTABLE)
 
-find_program(DOXYGEN_EXECUTABLE
-  NAMES doxygen
-  PATHS
-    "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\doxygen_is1;Inno Setup: App Path]/bin"
-    /Applications/Doxygen.app/Contents/Resources
-    /Applications/Doxygen.app/Contents/MacOS
-  DOC "Doxygen documentation generation tool (http://www.doxygen.org)"
-)
+    if(DOXYGEN_EXECUTABLE)
+        execute_process(
+            COMMAND "${DOXYGEN_EXECUTABLE}" --version
+            OUTPUT_VARIABLE DOXYGEN_VERSION
+            OUTPUT_STRIP_TRAILING_WHITESPACE
+            RESULT_VARIABLE result
+        )
+        if(result)
+            message(WARNING "Unable to determine doxygen version: ${result}")
+        endif()
 
-if(DOXYGEN_EXECUTABLE)
-  execute_process(COMMAND ${DOXYGEN_EXECUTABLE} "--version" OUTPUT_VARIABLE DOXYGEN_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
-endif()
+        # Create an imported target for Doxygen
+        add_executable(Doxygen::doxygen IMPORTED GLOBAL)
+        set_target_properties(Doxygen::doxygen PROPERTIES
+            IMPORTED_LOCATION "${DOXYGEN_EXECUTABLE}"
+        )
+    endif()
+endmacro()
 
-include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
-FIND_PACKAGE_HANDLE_STANDARD_ARGS(Doxygen REQUIRED_VARS DOXYGEN_EXECUTABLE VERSION_VAR DOXYGEN_VERSION)
-
-#
-# Find Dot...
-#
-
-set(_x86 "(x86)")
-file(GLOB _Doxygen_GRAPHVIZ_BIN_DIRS
-  "$ENV{ProgramFiles}/Graphviz*/bin"
-  "$ENV{ProgramFiles${_x86}}/Graphviz*/bin"
-  )
-unset(_x86)
-
-if(NOT DOXYGEN_SKIP_DOT)
-  find_program(DOXYGEN_DOT_EXECUTABLE
-    NAMES dot
-    PATHS
-      ${_Doxygen_GRAPHVIZ_BIN_DIRS}
-      "$ENV{ProgramFiles}/ATT/Graphviz/bin"
-      "C:/Program Files/ATT/Graphviz/bin"
-      [HKEY_LOCAL_MACHINE\\SOFTWARE\\ATT\\Graphviz;InstallPath]/bin
-      /Applications/Graphviz.app/Contents/MacOS
-      /Applications/Doxygen.app/Contents/Resources
-      /Applications/Doxygen.app/Contents/MacOS
-    DOC "Graphviz Dot tool for using Doxygen"
-  )
-
-  if(DOXYGEN_DOT_EXECUTABLE)
-    set(DOXYGEN_DOT_FOUND TRUE)
-    # The Doxyfile wants the path to Dot, not the entire path and executable
-    get_filename_component(DOXYGEN_DOT_PATH "${DOXYGEN_DOT_EXECUTABLE}" PATH)
-  endif()
+#
+# Find Diagram Editor...
+#
+macro(_Doxygen_find_dia)
+    set(_x86 "(x86)")
+    find_program(
+        DOXYGEN_DIA_EXECUTABLE
+        NAMES dia
+        PATHS
+            "$ENV{ProgramFiles}/Dia"
+            "$ENV{ProgramFiles${_x86}}/Dia"
+        DOC "Diagram Editor tool for use with Doxygen"
+    )
+    mark_as_advanced(DOXYGEN_DIA_EXECUTABLE)
+
+    if(DOXYGEN_DIA_EXECUTABLE)
+        # The Doxyfile wants the path to the utility, not the entire path
+        # including file name
+        get_filename_component(DOXYGEN_DIA_PATH
+                              "${DOXYGEN_DIA_EXECUTABLE}"
+                              DIRECTORY)
+        if(WIN32)
+            file(TO_NATIVE_PATH "${DOXYGEN_DIA_PATH}" DOXYGEN_DIA_PATH)
+        endif()
+
+        # Create an imported target for component
+        add_executable(Doxygen::dia IMPORTED GLOBAL)
+        set_target_properties(Doxygen::dia PROPERTIES
+            IMPORTED_LOCATION "${DOXYGEN_DIA_EXECUTABLE}"
+        )
+    endif()
+
+    unset(_x86)
+endmacro()
+
+#
+# Find Graphviz Dot...
+#
+macro(_Doxygen_find_dot)
+    set(_x86 "(x86)")
+    file(
+        GLOB _Doxygen_GRAPHVIZ_BIN_DIRS
+        "$ENV{ProgramFiles}/Graphviz*/bin"
+        "$ENV{ProgramFiles${_x86}}/Graphviz*/bin"
+    )
+    find_program(
+        DOXYGEN_DOT_EXECUTABLE
+        NAMES dot
+        PATHS
+            ${_Doxygen_GRAPHVIZ_BIN_DIRS}
+            "$ENV{ProgramFiles}/ATT/Graphviz/bin"
+            "C:/Program Files/ATT/Graphviz/bin"
+            [HKEY_LOCAL_MACHINE\\SOFTWARE\\ATT\\Graphviz;InstallPath]/bin
+            /Applications/Graphviz.app/Contents/MacOS
+            /Applications/Doxygen.app/Contents/Resources
+            /Applications/Doxygen.app/Contents/MacOS
+        DOC "Dot tool for use with Doxygen"
+    )
+    mark_as_advanced(DOXYGEN_DOT_EXECUTABLE)
+
+    if(DOXYGEN_DOT_EXECUTABLE)
+        # The Doxyfile wants the path to the utility, not the entire path
+        # including file name
+        get_filename_component(DOXYGEN_DOT_PATH
+                               "${DOXYGEN_DOT_EXECUTABLE}"
+                               DIRECTORY)
+        if(WIN32)
+            file(TO_NATIVE_PATH "${DOXYGEN_DOT_PATH}" DOXYGEN_DOT_PATH)
+        endif()
+
+        # Create an imported target for component
+        add_executable(Doxygen::dot IMPORTED GLOBAL)
+        set_target_properties(Doxygen::dot PROPERTIES
+            IMPORTED_LOCATION "${DOXYGEN_DOT_EXECUTABLE}"
+        )
+    endif()
 
+    unset(_Doxygen_GRAPHVIZ_BIN_DIRS)
+    unset(_x86)
+endmacro()
+
+#
+# Find Message Sequence Chart...
+#
+macro(_Doxygen_find_mscgen)
+    set(_x86 "(x86)")
+    find_program(
+        DOXYGEN_MSCGEN_EXECUTABLE
+        NAMES mscgen
+        PATHS
+            "$ENV{ProgramFiles}/Mscgen"
+            "$ENV{ProgramFiles${_x86}}/Mscgen"
+        DOC "Message sequence chart tool for use with Doxygen"
+    )
+    mark_as_advanced(DOXYGEN_MSCGEN_EXECUTABLE)
+
+    if(DOXYGEN_MSCGEN_EXECUTABLE)
+        # The Doxyfile wants the path to the utility, not the entire path
+        # including file name
+        get_filename_component(DOXYGEN_MSCGEN_PATH
+                               "${DOXYGEN_MSCGEN_EXECUTABLE}"
+                               DIRECTORY)
+        if(WIN32)
+            file(TO_NATIVE_PATH "${DOXYGEN_MSCGEN_PATH}" DOXYGEN_MSCGEN_PATH)
+        endif()
+
+        # Create an imported target for component
+        add_executable(Doxygen::mscgen IMPORTED GLOBAL)
+        set_target_properties(Doxygen::mscgen PROPERTIES
+            IMPORTED_LOCATION "${DOXYGEN_MSCGEN_EXECUTABLE}"
+        )
+    endif()
+
+    unset(_x86)
+endmacro()
+
+# Make sure `doxygen` is one of the components to find
+set(_Doxygen_keep_backward_compat FALSE)
+if(NOT Doxygen_FIND_COMPONENTS)
+    # Search at least for `doxygen` executable
+    set(Doxygen_FIND_COMPONENTS doxygen)
+    # Preserve backward compatibility:
+    # search for `dot` also if `DOXYGEN_SKIP_DOT` is not explicitly disable this.
+    if(NOT DOXYGEN_SKIP_DOT)
+        list(APPEND Doxygen_FIND_COMPONENTS dot)
+    endif()
+    set(_Doxygen_keep_backward_compat TRUE)
+elseif(NOT doxygen IN_LIST Doxygen_FIND_COMPONENTS)
+    list(INSERT Doxygen_FIND_COMPONENTS 0 doxygen)
 endif()
 
 #
-# Backwards compatibility...
+# Find all requested components of Doxygen...
 #
+foreach(_comp IN LISTS Doxygen_FIND_COMPONENTS)
+    if(_comp STREQUAL "doxygen")
+        _Doxygen_find_doxygen()
+    elseif(_comp STREQUAL "dia")
+        _Doxygen_find_dia()
+    elseif(_comp STREQUAL "dot")
+        _Doxygen_find_dot()
+    elseif(_comp STREQUAL "mscgen")
+        _Doxygen_find_mscgen()
+    else()
+        message(WARNING "${_comp} is not a valid Doxygen component")
+        set(Doxygen_${_comp}_FOUND FALSE)
+        continue()
+    endif()
+
+    if(TARGET Doxygen::${_comp})
+        set(Doxygen_${_comp}_FOUND TRUE)
+    else()
+        set(Doxygen_${_comp}_FOUND FALSE)
+    endif()
+endforeach()
+unset(_comp)
+
+# Verify find results
+include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
+find_package_handle_standard_args(
+    Doxygen
+    REQUIRED_VARS DOXYGEN_EXECUTABLE
+    VERSION_VAR DOXYGEN_VERSION
+    HANDLE_COMPONENTS
+)
 
+#
+# Backwards compatibility...
+#
 if(APPLE)
-  # Restore the old app-bundle setting setting
-  set(CMAKE_FIND_APPBUNDLE ${TEMP_DOXYGEN_SAVE_CMAKE_FIND_APPBUNDLE})
+    # Restore the old app-bundle setting
+    set(CMAKE_FIND_APPBUNDLE ${TEMP_DOXYGEN_SAVE_CMAKE_FIND_APPBUNDLE})
 endif()
 
-# Maintain the _FOUND variables as "YES" or "NO" for backwards compatibility
-# (allows people to stuff them directly into Doxyfile with configure_file())
+# Maintain the _FOUND variables as "YES" or "NO" for backwards
+# compatibility. This allows people to substitute them directly into
+# Doxyfile with configure_file().
 if(DOXYGEN_FOUND)
-  set(DOXYGEN_FOUND "YES")
+    set(DOXYGEN_FOUND "YES")
 else()
-  set(DOXYGEN_FOUND "NO")
+    set(DOXYGEN_FOUND "NO")
 endif()
-if(DOXYGEN_DOT_FOUND)
-  set(DOXYGEN_DOT_FOUND "YES")
-else()
-  set(DOXYGEN_DOT_FOUND "NO")
+if(_Doxygen_keep_backward_compat)
+    if(DOXYGEN_DOT_FOUND)
+        set(DOXYGEN_DOT_FOUND "YES")
+    else()
+        set(DOXYGEN_DOT_FOUND "NO")
+    endif()
+
+    # For backwards compatibility support for even older CMake versions
+    set(DOXYGEN ${DOXYGEN_EXECUTABLE})
+    set(DOT ${DOXYGEN_DOT_EXECUTABLE})
+
+    # No need to keep any backward compatibility for `DOXYGEN_MSCGEN_XXX`
+    # and `DOXYGEN_DIA_XXX` since they were not supported before component
+    # support was added
 endif()
+unset(_Doxygen_keep_backward_compat)
 
-# For backwards compatibility support
-set (DOXYGEN ${DOXYGEN_EXECUTABLE} )
-set (DOT ${DOXYGEN_DOT_EXECUTABLE} )
+#
+# Allow full control of Doxygen from CMakeLists.txt
+#
+
+# Prepare a template Doxyfile and Doxygen's default values CMake file
+if(TARGET Doxygen::doxygen)
+    # If doxygen was found, use it to generate a minimal default Doxyfile.
+    # We will delete this file after we have finished using it below to
+    # generate the other files that doxygen_add_docs() will use.
+    set(_Doxygen_tpl "${PROJECT_BINARY_DIR}/CMakeDoxyfile.tpl")
+    execute_process(
+        COMMAND "${DOXYGEN_EXECUTABLE}" -s -g "${_Doxygen_tpl}"
+        OUTPUT_QUIET
+        RESULT_VARIABLE result
+    )
+    if(result)
+        message(FATAL_ERROR
+                "Unable to generate Doxyfile template: ${result}")
+    elseif(NOT EXISTS "${_Doxygen_tpl}")
+        message(FATAL_ERROR
+                "Doxygen has failed to generate a Doxyfile template")
+    endif()
+
+    # Write a do-not-edit header to files we are going to generate...
+    set(_Doxygen_dne_header
+[[
+#
+# DO NOT EDIT! THIS FILE WAS GENERATED BY CMAKE!
+#
+
+]]
+    )
+    # We only need one copy of these across the whole build, since their
+    # content is only dependent on the version of Doxygen being used. Therefore
+    # we always put them at the top of the build tree so that they are in a
+    # predictable location.
+    set(_doxyfile_in       "${PROJECT_BINARY_DIR}/CMakeDoxyfile.in")
+    set(_doxyfile_defaults "${PROJECT_BINARY_DIR}/CMakeDoxygenDefaults.cmake")
+
+    file(WRITE "${_doxyfile_in}"       ${_Doxygen_dne_header})
+    file(WRITE "${_doxyfile_defaults}" ${_Doxygen_dne_header})
+
+    # Get strings containing a configuration key from the template Doxyfile
+    # we obtained from this version of Doxygen. Because some options are split
+    # across multiple lines by ending lines with backslashes, we cannot just
+    # use file(STRINGS...) with a REGEX. Instead, read lines without a REGEX
+    # so that file(STRINGS...) handles the trailing backslash as a line
+    # continuation. It stores multi-lines as lists, so we then have to replace
+    # the ";" list separator with backslashed newlines again so that we get the
+    # original content stored back as the value part.
+    file(STRINGS "${_Doxygen_tpl}" _file_lines)
+    unset(_Doxygen_tpl_params)
+    foreach(_line IN LISTS _file_lines)
+        if(_line MATCHES "([A-Z][A-Z0-9_]+)( *=)(.*)")
+            set(_key "${CMAKE_MATCH_1}")
+            set(_eql "${CMAKE_MATCH_2}")
+            string(REPLACE ";" "\\\n" _value "${CMAKE_MATCH_3}")
+            list(APPEND _Doxygen_tpl_params "${_key}${_eql}${_value}")
+        endif()
+    endforeach()
+
+    # Build up a Doxyfile that provides @configVar@ substitutions for each
+    # Doxygen config option as well as a separate CMake script which provides
+    # the default value for each of those options if the project doesn't supply
+    # them. Each config option will support substitution of a CMake variable
+    # of the same name except with DOXYGEN_ prepended.
+    foreach(_Doxygen_param IN LISTS _Doxygen_tpl_params)
+        if(_Doxygen_param MATCHES "([A-Z][A-Z0-9_]+)( *)=( (.*))?")
+            # Ok, this is a config key with a value
+            if(CMAKE_MATCH_COUNT EQUAL 4)
+                file(APPEND "${_doxyfile_in}"
+                    "${CMAKE_MATCH_1}${CMAKE_MATCH_2}= @DOXYGEN_${CMAKE_MATCH_1}@\n")
+                # Remove the backslashes we had to preserve to handle newlines
+                string(REPLACE "\\\n" "\n" _value "${CMAKE_MATCH_4}")
+                file(APPEND "${_doxyfile_defaults}"
+"if(NOT DEFINED DOXYGEN_${CMAKE_MATCH_1})
+    set(DOXYGEN_${CMAKE_MATCH_1} ${_value})
+endif()
+")
+            # Ok, this is a config key with empty default value
+            elseif(CMAKE_MATCH_COUNT EQUAL 2)
+                file(APPEND "${_doxyfile_in}"
+                    "${CMAKE_MATCH_1}${CMAKE_MATCH_2}= @DOXYGEN_${CMAKE_MATCH_1}@\n")
+            else()
+                message(AUTHOR_WARNING
+"Unexpected line format! Code review required!\nFault line: ${_Doxygen_param}")
+            endif()
+        else()
+            message(AUTHOR_WARNING
+"Unexpected line format! Code review required!\nFault line: ${_Doxygen_param}")
+        endif()
+    endforeach()
+
+    # Ok, dumped defaults are not needed anymore...
+    file(REMOVE "${_Doxygen_tpl}")
+
+    unset(_Doxygen_param)
+    unset(_Doxygen_tpl_params)
+    unset(_Doxygen_dne_header)
+    unset(_Doxygen_tpl)
+
+endif()
+
+function(doxygen_quote_value VARIABLE)
+    # Quote a value of the given variable if:
+    # - variable parameter was really given
+    # - a variable it points is defined
+    # - a value doesn't quoted already
+    # - and it has spaces
+    if(VARIABLE AND DEFINED ${VARIABLE} AND
+       NOT ${VARIABLE} MATCHES "^\".* .*\"$" AND ${VARIABLE} MATCHES " ")
+        set(${VARIABLE} "\"${${VARIABLE}}\"" PARENT_SCOPE)
+    endif()
+endfunction()
+
+function(doxygen_list_to_quoted_strings LIST_VARIABLE)
+    if(LIST_VARIABLE AND DEFINED ${LIST_VARIABLE})
+        unset(_inputs)
+        unset(_sep)
+        foreach(_in IN LISTS ${LIST_VARIABLE})
+            doxygen_quote_value(_in)
+            string(APPEND _inputs "${_sep}${_in}")
+            set(_sep " ")
+        endforeach()
+        set(${LIST_VARIABLE} "${_inputs}" PARENT_SCOPE)
+    endif()
+endfunction()
+
+function(doxygen_add_docs targetName)
+    set(_options)
+    set(_one_value_args WORKING_DIRECTORY COMMENT)
+    set(_multi_value_args)
+    cmake_parse_arguments(_args
+                          "${_options}"
+                          "${_one_value_args}"
+                          "${_multi_value_args}"
+                          ${ARGN})
+
+    if(NOT _args_COMMENT)
+        set(_args_COMMENT "Generate API documentation for ${targetName}")
+    endif()
+
+    if(NOT _args_WORKING_DIRECTORY)
+        set(_args_WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
+    endif()
+
+    if(DEFINED DOXYGEN_INPUT)
+        message(WARNING
+"DOXYGEN_INPUT is set but it will be ignored. Pass the files and directories \
+directly to the doxygen_add_docs() command instead.")
+    endif()
+    set(DOXYGEN_INPUT ${_args_UNPARSED_ARGUMENTS})
+
+    if(NOT TARGET Doxygen::doxygen)
+        message(FATAL_ERROR "Doxygen was not found, needed by \
+doxygen_add_docs() for target ${targetName}")
+    endif()
+
+    # If not already defined, set some relevant defaults based on the
+    # assumption that the documentation is for the whole project. Details
+    # specified in the project() command will be used to populate a number of
+    # these defaults.
+
+    if(NOT DEFINED DOXYGEN_PROJECT_NAME)
+        # The PROJECT_NAME tag is a single word (or a sequence of words
+        # surrounded by double-quotes, unless you are using Doxywizard) that
+        # should identify the project for which the documentation is generated.
+        # This name is used in the title of most generated pages and in a few
+        # other places. The default value is: My Project.
+        set(DOXYGEN_PROJECT_NAME ${PROJECT_NAME})
+    endif()
+
+    if(NOT DEFINED DOXYGEN_PROJECT_NUMBER)
+        # The PROJECT_NUMBER tag can be used to enter a project or revision
+        # number. This could be handy for archiving the generated documentation
+        # or if some version control system is used.
+        set(DOXYGEN_PROJECT_NUMBER ${PROJECT_VERSION})
+    endif()
+
+    if(NOT DEFINED DOXYGEN_PROJECT_BRIEF)
+        # Using the PROJECT_BRIEF tag one can provide an optional one line
+        # description for a project that appears at the top of each page and
+        # should give viewer a quick idea about the purpose of the project.
+        # Keep the description short.
+        set(DOXYGEN_PROJECT_BRIEF "${PROJECT_DESCRIPTION}")
+    endif()
+
+    if(NOT DEFINED DOXYGEN_RECURSIVE)
+        # The RECURSIVE tag can be used to specify whether or not
+        # subdirectories should be searched for input files as well. CMake
+        # projects generally evolve to span multiple directories, so it makes
+        # more sense for this to be on by default. Doxygen's default value
+        # has this setting turned off, so we override it.
+        set(DOXYGEN_RECURSIVE YES)
+    endif()
+
+    if(NOT DEFINED DOXYGEN_OUTPUT_DIRECTORY)
+        # The OUTPUT_DIRECTORY tag is used to specify the (relative or
+        # absolute) path into which the generated documentation will be
+        # written. If a relative path is used, Doxygen will interpret it as
+        # being relative to the location where doxygen was started, but we need
+        # to run Doxygen in the source tree so that relative input paths work
+        # intuitively. Therefore, we ensure that the output directory is always
+        # an absolute path and if the project provided a relative path, we
+        # treat it as relative to the current BINARY directory so that output
+        # is not generated inside the source tree.
+        set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
+    elseif(NOT IS_ABSOLUTE "${DOXYGEN_OUTPUT_DIRECTORY}")
+        get_filename_component(DOXYGEN_OUTPUT_DIRECTORY
+                               "${DOXYGEN_OUTPUT_DIRECTORY}"
+                               ABSOLUTE
+                               BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+    endif()
+
+    if(NOT DEFINED DOXYGEN_HAVE_DOT)
+        # If you set the HAVE_DOT tag to YES then doxygen will assume the dot
+        # tool is available from the path. This tool is part of Graphviz (see:
+        # http://www.graphviz.org/), a graph visualization toolkit from AT&T
+        # and Lucent Bell Labs. The other options in this section have no
+        # effect if this option is set to NO.
+        # Doxygen's default value is: NO.
+        set(DOXYGEN_HAVE_DOT ${DOXYGEN_DOT_FOUND})
+    endif()
+
+    if(NOT DEFINED DOXYGEN_DOT_MULTI_TARGETS)
+        # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate
+        # multiple output files in one run (i.e. multiple -o and -T options on
+        # the command line). This makes dot run faster, but since only newer
+        # versions of dot (>1.8.10) support this, Doxygen disables this feature
+        # by default.
+        # This tag requires that the tag HAVE_DOT is set to YES.
+        set(DOXYGEN_DOT_MULTI_TARGETS YES)
+    endif()
+
+    if(NOT DEFINED DOXYGEN_GENERATE_LATEX)
+        # If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX
+        # output. We only want the HTML output enabled by default, so we turn
+        # this off if the project hasn't specified it.
+        set(DOXYGEN_GENERATE_LATEX NO)
+    endif()
+
+    if(NOT DEFINED DOXYGEN_WARN_FORMAT)
+        if(CMAKE_VS_MSBUILD_COMMAND OR CMAKE_VS_DEVENV_COMMAND)
+            # The WARN_FORMAT tag determines the format of the warning messages
+            # that doxygen can produce. The string should contain the $file,
+            # $line and $text tags, which will be replaced by the file and line
+            # number from which the warning originated and the warning text.
+            # Optionally, the format may contain $version, which will be
+            # replaced by the version of the file (if it could be obtained via
+            # FILE_VERSION_FILTER).
+            # Doxygen's default value is: $file:$line: $text
+            set(DOXYGEN_WARN_FORMAT "$file($line) : $text ")
+        endif()
+    endif()
+
+    if(DEFINED DOXYGEN_WARN_LOGFILE AND NOT IS_ABSOLUTE "${DOXYGEN_WARN_LOGFILE}")
+        # The WARN_LOGFILE tag can be used to specify a file to which warning and error
+        # messages should be written. If left blank the output is written to standard
+        # error (stderr).
+        get_filename_component(DOXYGEN_WARN_LOGFILE
+                               "${DOXYGEN_WARN_LOGFILE}"
+                               ABSOLUTE
+                               BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+    endif()
+
+    # Any files from the INPUT that match any of the EXCLUDE_PATTERNS will be
+    # excluded from the set of input files. We provide some additional patterns
+    # to prevent commonly unwanted things from CMake builds being pulled in.
+    #
+    # Note that the wildcards are matched against the file with absolute path,
+    # so to exclude all test directories for example use the pattern */test/*
+    list(
+        APPEND
+        DOXYGEN_EXCLUDE_PATTERNS
+        "*/.git/*"
+        "*/.svn/*"
+        "*/.hg/*"
+        "*/CMakeFiles/*"
+        "*/_CPack_Packages/*"
+        "DartConfiguration.tcl"
+        "CMakeLists.txt"
+        "CMakeCache.txt"
+    )
+
+    # Now bring in Doxgen's defaults for those things the project has not
+    # already set and we have not provided above
+    include("${PROJECT_BINARY_DIR}/CMakeDoxygenDefaults.cmake" OPTIONAL)
+
+    # Cleanup built HTMLs on "make clean"
+    # TODO Any other dirs?
+    if(DOXYGEN_GENERATE_HTML)
+        if(IS_ABSOLUTE "${DOXYGEN_HTML_OUTPUT}")
+            set(_args_clean_html_dir "${DOXYGEN_HTML_OUTPUT}")
+        else()
+            set(_args_clean_html_dir
+                "${DOXYGEN_OUTPUT_DIRECTORY}/${DOXYGEN_HTML_OUTPUT}")
+        endif()
+        set_property(DIRECTORY APPEND PROPERTY
+            ADDITIONAL_MAKE_CLEAN_FILES "${_args_clean_html_dir}")
+    endif()
+
+    # Build up a list of files we can identify from the inputs so we can list
+    # them as SOURCES in the custom target (makes them display in IDEs). We
+    # must do this before we transform the various DOXYGEN_... variables below
+    # because we need to process DOXYGEN_INPUT as a list first.
+    unset(_sources)
+    foreach(_item IN LISTS DOXYGEN_INPUT)
+        get_filename_component(_abs_item "${_item}" ABSOLUTE
+                               BASE_DIR "${_args_WORKING_DIRECTORY}")
+        if(EXISTS "${_abs_item}" AND
+           NOT IS_DIRECTORY "${_abs_item}" AND
+           NOT IS_SYMLINK "${_abs_item}")
+            list(APPEND _sources "${_abs_item}")
+        endif()
+    endforeach()
+    if(_sources)
+        list(INSERT _sources 0 SOURCES)
+    endif()
+
+    # Transform known list type options into space separated strings.
+    set(_doxygen_list_options
+        ABBREVIATE_BRIEF
+        ALIASES
+        CITE_BIB_FILES
+        DIAFILE_DIRS
+        DOTFILE_DIRS
+        DOT_FONTPATH
+        ENABLED_SECTIONS
+        EXAMPLE_PATH
+        EXAMPLE_PATTERNS
+        EXCLUDE
+        EXCLUDE_PATTERNS
+        EXCLUDE_SYMBOLS
+        EXPAND_AS_DEFINED
+        EXTENSION_MAPPING
+        EXTRA_PACKAGES
+        EXTRA_SEARCH_MAPPINGS
+        FILE_PATTERNS
+        FILTER_PATTERNS
+        FILTER_SOURCE_PATTERNS
+        HTML_EXTRA_FILES
+        HTML_EXTRA_STYLESHEET
+        IGNORE_PREFIX
+        IMAGE_PATH
+        INCLUDE_FILE_PATTERNS
+        INCLUDE_PATH
+        INPUT
+        LATEX_EXTRA_FILES
+        LATEX_EXTRA_STYLESHEET
+        MATHJAX_EXTENSIONS
+        MSCFILE_DIRS
+        PLANTUML_INCLUDE_PATH
+        PREDEFINED
+        QHP_CUST_FILTER_ATTRS
+        QHP_SECT_FILTER_ATTRS
+        STRIP_FROM_INC_PATH
+        STRIP_FROM_PATH
+        TAGFILES
+        TCL_SUBST
+    )
+    foreach(_item IN LISTS _doxygen_list_options)
+        doxygen_list_to_quoted_strings(DOXYGEN_${_item})
+    endforeach()
+
+    # Transform known single value variables which may contain spaces, such as
+    # paths or description strings.
+    set(_doxygen_quoted_options
+        CHM_FILE
+        DIA_PATH
+        DOCBOOK_OUTPUT
+        DOCSET_FEEDNAME
+        DOCSET_PUBLISHER_NAME
+        DOT_FONTNAME
+        DOT_PATH
+        EXTERNAL_SEARCH_ID
+        FILE_VERSION_FILTER
+        GENERATE_TAGFILE
+        HHC_LOCATION
+        HTML_FOOTER
+        HTML_HEADER
+        HTML_OUTPUT
+        HTML_STYLESHEET
+        INPUT_FILTER
+        LATEX_FOOTER
+        LATEX_HEADER
+        LATEX_OUTPUT
+        LAYOUT_FILE
+        MAN_OUTPUT
+        MAN_SUBDIR
+        MATHJAX_CODEFILE
+        MSCGEN_PATH
+        OUTPUT_DIRECTORY
+        PERL_PATH
+        PLANTUML_JAR_PATH
+        PROJECT_BRIEF
+        PROJECT_LOGO
+        PROJECT_NAME
+        QCH_FILE
+        QHG_LOCATION
+        QHP_CUST_FILTER_NAME
+        QHP_VIRTUAL_FOLDER
+        RTF_EXTENSIONS_FILE
+        RTF_OUTPUT
+        RTF_STYLESHEET_FILE
+        SEARCHDATA_FILE
+        USE_MDFILE_AS_MAINPAGE
+        WARN_FORMAT
+        WARN_LOGFILE
+        XML_OUTPUT
+    )
+    foreach(_item IN LISTS _doxygen_quoted_options)
+        doxygen_quote_value(DOXYGEN_${_item})
+    endforeach()
+
+    # Prepare doxygen configuration file
+    set(_doxyfile_template "${PROJECT_BINARY_DIR}/CMakeDoxyfile.in")
+    set(_target_doxyfile "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.${targetName}")
+    configure_file("${_doxyfile_template}" "${_target_doxyfile}")
+
+    # Add the target
+    add_custom_target(
+        ${targetName}
+        COMMAND "${DOXYGEN_EXECUTABLE}" "${_target_doxyfile}"
+        WORKING_DIRECTORY "${_args_WORKING_DIRECTORY}"
+        DEPENDS "${_target_doxyfile}"
+        COMMENT "${_args_COMMENT}"
+        ${_sources}
+    )
 
-mark_as_advanced(
-  DOXYGEN_EXECUTABLE
-  DOXYGEN_DOT_EXECUTABLE
-  )
+endfunction()

+ 4 - 0
Tests/CMakeLists.txt

@@ -1385,6 +1385,10 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
     add_subdirectory(FindBZip2)
   endif()
 
+  if(CMake_TEST_FindDoxygen)
+    add_subdirectory(FindDoxygen)
+  endif()
+
   if(CMake_TEST_FindGSL)
     add_subdirectory(FindGSL)
   endif()

+ 20 - 0
Tests/FindDoxygen/CMakeLists.txt

@@ -0,0 +1,20 @@
+add_test(NAME FindDoxygen.SimpleTest COMMAND
+  ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+  --build-and-test
+  "${CMake_SOURCE_DIR}/Tests/FindDoxygen/SimpleTest"
+  "${CMake_BINARY_DIR}/Tests/FindDoxygen/SimpleTest"
+  --build-target allDocTargets
+  ${build_generator_args}
+  --build-options ${build_options}
+)
+
+if(CMake_TEST_FindDoxygen_Dot)
+  add_test(NAME FindDoxygen.DotComponentTest COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindDoxygen/DotComponentTestTest"
+    "${CMake_BINARY_DIR}/Tests/FindDoxygen/DotComponentTestTest"
+    ${build_generator_args}
+    --build-options ${build_options}
+  )
+endif()

+ 18 - 0
Tests/FindDoxygen/DotComponentTestTest/CMakeLists.txt

@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.8)
+project(TestFindDoxygenDot VERSION 1.0 LANGUAGES NONE)
+
+# Testing a new signature w/ components
+find_package(Doxygen REQUIRED COMPONENTS dot)
+
+# No backwards compatibility expected when component form is used
+if(TARGET Doxygen::doxygen)
+    if(DOXYGEN)
+        message(FATAL_ERROR "DOXYGEN unexpectedly defined with component form")
+    endif()
+else()
+    message(FATAL_ERROR "Import target Doxygen::doxygen not defined")
+endif()
+
+if(NOT TARGET Doxygen::dot)
+    message(FATAL_ERROR "Import target Doxygen::dot not defined")
+endif()

+ 59 - 0
Tests/FindDoxygen/SimpleTest/CMakeLists.txt

@@ -0,0 +1,59 @@
+cmake_minimum_required(VERSION 3.8)
+project(TestFindDoxygen VERSION 1.0 LANGUAGES NONE)
+
+# Testing backward compatible signature
+find_package(Doxygen REQUIRED)
+
+if(TARGET Doxygen::doxygen)
+    # Check backward compatibility
+    if(NOT DOXYGEN_EXECUTABLE)
+        message(FATAL_ERROR "Backward compatibility broken: DOXYGEN_EXECUTABLE not set")
+    endif()
+    if(NOT DOXYGEN)
+        message(FATAL_ERROR "Backward compatibility broken: DOXYGEN not set")
+    endif()
+    # Check presence of expected generated files
+    foreach(file CMakeDoxyfile.in CMakeDoxygenDefaults.cmake)
+        if(NOT EXISTS "${PROJECT_BINARY_DIR}/${file}")
+            message(FATAL_ERROR "Missing generated file: ${file}")
+        endif()
+    endforeach()
+else()
+    message(FATAL_ERROR "Import target Doxygen::doxygen not defined")
+endif()
+
+doxygen_add_docs(docsNoArgs)
+if(NOT EXISTS "${PROJECT_BINARY_DIR}/Doxyfile.docsNoArgs")
+    message(FATAL_ERROR "Missing generated file: Doxyfile.docsNoArgs")
+endif()
+if(NOT TARGET docsNoArgs)
+    message(FATAL_ERROR "Target docsNoArgs not created")
+endif()
+
+configure_file(spaces_in_name.cpp.in "spaces in name.cpp" COPYONLY)
+doxygen_add_docs(docsWithArgs
+    "${CMAKE_CURRENT_BINARY_DIR}/spaces in name.cpp"
+    main.cpp
+)
+if(NOT EXISTS "${PROJECT_BINARY_DIR}/Doxyfile.docsWithArgs")
+    message(FATAL_ERROR "Missing generated file: Doxyfile.docsWithArgs")
+endif()
+if(NOT TARGET docsWithArgs)
+    message(FATAL_ERROR "Target docsWithArgs not created")
+endif()
+# Note that CMake inserts at least one entry into SOURCES when a COMMAND or
+# DEPENDS option is given to add_custom_target(), so rather than looking for an
+# exact match, we only verify that the files we expect to be there are present
+get_target_property(srcList docsWithArgs SOURCES)
+set(expectedList
+    "${CMAKE_CURRENT_BINARY_DIR}/spaces in name.cpp"
+    "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp"
+)
+foreach(f IN LISTS expectedList)
+    if(NOT f IN_LIST srcList)
+        message(FATAL_ERROR "SOURCES missing file: ${f}")
+    endif()
+endforeach()
+
+add_custom_target(allDocTargets)
+add_dependencies(allDocTargets docsNoArgs docsWithArgs)

+ 4 - 0
Tests/FindDoxygen/SimpleTest/main.cpp

@@ -0,0 +1,4 @@
+/**
+ * \file
+ * \brief One C++ file w/ sample Doxygen comment just to produce any docs...
+ */

+ 4 - 0
Tests/FindDoxygen/SimpleTest/spaces_in_name.cpp.in

@@ -0,0 +1,4 @@
+/**
+ * \file
+ * \brief This file name contains spaces
+ */