vtk-m/CMake/FindSphinx.cmake

396 lines
13 KiB
CMake
Raw Normal View History

# This find module originally created by Jeroen Koekkoek.
# Copyright (c) 2023, Jeroen Koekkoek
#
# This file is covered by a BSD 3-Clause License.
# See https://github.com/k0ekk0ek/cmake-sphinx/blob/master/LICENSE for details.
include(FindPackageHandleStandardArgs)
macro(_Sphinx_find_executable _exe)
string(TOUPPER "${_exe}" _uc)
# sphinx-(build|quickstart)-3 x.x.x
# FIXME: This works on Fedora (and probably most other UNIX like targets).
# Windows targets and PIP installs might need some work.
find_program(
SPHINX_${_uc}_EXECUTABLE
NAMES "sphinx-${_exe}-3" "sphinx-${_exe}" "sphinx-${_exe}.exe")
mark_as_advanced(SPHINX_${_uc}_EXECUTABLE)
if(SPHINX_${_uc}_EXECUTABLE)
execute_process(
COMMAND "${SPHINX_${_uc}_EXECUTABLE}" --version
RESULT_VARIABLE _result
OUTPUT_VARIABLE _output
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(_result EQUAL 0 AND _output MATCHES " v?([0-9]+\\.[0-9]+\\.[0-9]+)$")
set(SPHINX_${_uc}_VERSION "${CMAKE_MATCH_1}")
endif()
if(NOT TARGET Sphinx::${_exe})
add_executable(Sphinx::${_exe} IMPORTED GLOBAL)
set_target_properties(Sphinx::${_exe} PROPERTIES
IMPORTED_LOCATION "${SPHINX_${_uc}_EXECUTABLE}")
endif()
set(Sphinx_${_exe}_FOUND TRUE)
else()
set(Sphinx_${_exe}_FOUND FALSE)
endif()
unset(_uc)
endmacro()
macro(_Sphinx_find_module _name _module)
string(TOUPPER "${_name}" _Sphinx_uc)
if(SPHINX_PYTHON_EXECUTABLE)
execute_process(
COMMAND ${SPHINX_PYTHON_EXECUTABLE} -m ${_module} --version
RESULT_VARIABLE _result
OUTPUT_VARIABLE _output
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
if(_result EQUAL 0)
if(_output MATCHES " v?([0-9]+\\.[0-9]+\\.[0-9]+)$")
set(SPHINX_${_Sphinx_uc}_VERSION "${CMAKE_MATCH_1}")
endif()
if(NOT TARGET Sphinx::${_name})
set(SPHINX_${_Sphinx_uc}_EXECUTABLE "${SPHINX_PYTHON_EXECUTABLE} -m ${_module}")
add_executable(Sphinx::${_name} IMPORTED GLOBAL)
set_target_properties(Sphinx::${_name} PROPERTIES
IMPORTED_LOCATION "${SPHINX_PYTHON_EXECUTABLE}")
endif()
set(Sphinx_${_name}_ARGS -m ${_module})
set(Sphinx_${_name}_FOUND TRUE)
else()
set(Sphinx_${_name}_FOUND FALSE)
endif()
else()
set(Sphinx_${_name}_FOUND FALSE)
endif()
unset(_Sphinx_uc)
endmacro()
macro(_Sphinx_find_extension _ext)
if(SPHINX_PYTHON_EXECUTABLE)
execute_process(
COMMAND ${SPHINX_PYTHON_EXECUTABLE} -c "import ${_ext}"
RESULT_VARIABLE _result)
if(_result EQUAL 0)
set(Sphinx_${_ext}_FOUND TRUE)
else()
set(Sphinx_${_ext}_FOUND FALSE)
endif()
endif()
endmacro()
#
# Find sphinx-build and sphinx-quickstart.
#
# Find sphinx-build shim.
_Sphinx_find_executable(build)
if(SPHINX_BUILD_EXECUTABLE)
# Find sphinx-quickstart shim.
_Sphinx_find_executable(quickstart)
# Locate Python executable
if(CMAKE_HOST_WIN32)
# script-build on Windows located under (when PIP is used):
# C:/Program Files/PythonXX/Scripts
# C:/Users/username/AppData/Roaming/Python/PythonXX/Sripts
#
# Python modules are installed under:
# C:/Program Files/PythonXX/Lib
# C:/Users/username/AppData/Roaming/Python/PythonXX/site-packages
#
# To verify a given module is installed, use the Python base directory
# and test if either Lib/module.py or site-packages/module.py exists.
get_filename_component(_Sphinx_directory "${SPHINX_BUILD_EXECUTABLE}" DIRECTORY)
get_filename_component(_Sphinx_directory "${_Sphinx_directory}" DIRECTORY)
if(EXISTS "${_Sphinx_directory}/python.exe")
set(SPHINX_PYTHON_EXECUTABLE "${_Sphinx_directory}/python.exe")
endif()
unset(_Sphinx_directory)
else()
file(READ "${SPHINX_BUILD_EXECUTABLE}" _Sphinx_script)
if(_Sphinx_script MATCHES "^#!([^\n]+)")
string(STRIP "${CMAKE_MATCH_1}" _Sphinx_shebang)
if(EXISTS "${_Sphinx_shebang}")
set(SPHINX_PYTHON_EXECUTABLE "${_Sphinx_shebang}")
endif()
endif()
unset(_Sphinx_script)
unset(_Sphinx_shebang)
endif()
endif()
if(NOT SPHINX_PYTHON_EXECUTABLE)
# Python executable cannot be extracted from shim shebang or path if e.g.
# virtual environments are used, fallback to find package. Assume the
# correct installation is found, the setup is probably broken in more ways
# than one otherwise.
find_package(Python3 QUIET COMPONENTS Interpreter)
if(TARGET Python3::Interpreter)
set(SPHINX_PYTHON_EXECUTABLE ${Python3_EXECUTABLE})
# Revert to "python -m sphinx" if shim cannot be found.
if(NOT SPHINX_BUILD_EXECUTABLE)
_Sphinx_find_module(build sphinx)
_Sphinx_find_module(quickstart sphinx.cmd.quickstart)
endif()
endif()
endif()
#
# Verify components are available.
#
if(SPHINX_BUILD_VERSION)
# Breathe is required for Exhale
if("exhale" IN_LIST Sphinx_FIND_COMPONENTS AND NOT
"breathe" IN_LIST Sphinx_FIND_COMPONENTS)
list(APPEND Sphinx_FIND_COMPONENTS "breathe")
endif()
foreach(_Sphinx_component IN LISTS Sphinx_FIND_COMPONENTS)
if(_Sphinx_component STREQUAL "build")
# Do nothing, sphinx-build is always required.
continue()
elseif(_Sphinx_component STREQUAL "quickstart")
# Do nothing, sphinx-quickstart is optional, but looked up by default.
continue()
endif()
_Sphinx_find_extension(${_Sphinx_component})
endforeach()
unset(_Sphinx_component)
#
# Verify both executables are part of the Sphinx distribution.
#
if(SPHINX_QUICKSTART_VERSION AND NOT SPHINX_BUILD_VERSION STREQUAL SPHINX_QUICKSTART_VERSION)
message(FATAL_ERROR "Versions for sphinx-build (${SPHINX_BUILD_VERSION}) "
"and sphinx-quickstart (${SPHINX_QUICKSTART_VERSION}) "
"do not match")
endif()
endif()
find_package_handle_standard_args(
Sphinx
VERSION_VAR SPHINX_BUILD_VERSION
REQUIRED_VARS SPHINX_BUILD_EXECUTABLE SPHINX_BUILD_VERSION
HANDLE_COMPONENTS)
# Generate a conf.py template file using sphinx-quickstart.
#
# sphinx-quickstart allows for quiet operation and a lot of settings can be
# specified as command line arguments, therefore its not required to parse the
# generated conf.py.
function(_Sphinx_generate_confpy _target _cachedir)
if(NOT TARGET Sphinx::quickstart)
message(FATAL_ERROR "sphinx-quickstart is not available, needed by"
"sphinx_add_docs for target ${_target}")
endif()
if(NOT DEFINED SPHINX_PROJECT)
set(SPHINX_PROJECT ${PROJECT_NAME})
endif()
if(NOT DEFINED SPHINX_AUTHOR)
set(SPHINX_AUTHOR "${SPHINX_PROJECT} committers")
endif()
if(NOT DEFINED SPHINX_COPYRIGHT)
string(TIMESTAMP "%Y, ${SPHINX_AUTHOR}" SPHINX_COPYRIGHT)
endif()
if(NOT DEFINED SPHINX_VERSION)
set(SPHINX_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
endif()
if(NOT DEFINED SPHINX_RELEASE)
set(SPHINX_RELEASE "${PROJECT_VERSION}")
endif()
if(NOT DEFINED SPHINX_LANGUAGE)
set(SPHINX_LANGUAGE "en")
endif()
if(NOT DEFINED SPHINX_MASTER)
set(SPHINX_MASTER "index")
endif()
set(_known_exts autodoc doctest intersphinx todo coverage imgmath mathjax
ifconfig viewcode githubpages)
if(DEFINED SPHINX_EXTENSIONS)
foreach(_ext ${SPHINX_EXTENSIONS})
set(_is_known_ext FALSE)
foreach(_known_ext ${_known_exsts})
if(_ext STREQUAL _known_ext)
set(_opts "${opts} --ext-${_ext}")
set(_is_known_ext TRUE)
break()
endif()
endforeach()
if(NOT _is_known_ext)
if(_exts)
set(_exts "${_exts},${_ext}")
else()
set(_exts "${_ext}")
endif()
endif()
endforeach()
endif()
if(_exts)
set(_exts "--extensions=${_exts}")
endif()
set(_templatedir "${CMAKE_CURRENT_BINARY_DIR}/${_target}.template")
file(MAKE_DIRECTORY "${_templatedir}")
string(REPLACE " " ";" _Sphinx_executable ${SPHINX_QUICKSTART_EXECUTABLE})
execute_process(
COMMAND ${_Sphinx_executable}
-q --no-makefile --no-batchfile
-p "${SPHINX_PROJECT}"
-a "${SPHINX_AUTHOR}"
-v "${SPHINX_VERSION}"
-r "${SPHINX_RELEASE}"
-l "${SPHINX_LANGUAGE}"
--master "${SPHINX_MASTER}"
${_opts} ${_exts} "${_templatedir}"
RESULT_VARIABLE _result
OUTPUT_QUIET)
unset(_Sphinx_executable)
if(_result EQUAL 0 AND EXISTS "${_templatedir}/conf.py")
file(COPY "${_templatedir}/conf.py" DESTINATION "${_cachedir}")
endif()
file(REMOVE_RECURSE "${_templatedir}")
if(NOT _result EQUAL 0 OR NOT EXISTS "${_cachedir}/conf.py")
message(FATAL_ERROR "Sphinx configuration file not generated for "
"target ${_target}")
endif()
endfunction()
function(sphinx_add_docs _target)
set(_opts)
set(_single_opts BUILDER OUTPUT_DIRECTORY SOURCE_DIRECTORY)
set(_multi_opts BREATHE_PROJECTS)
cmake_parse_arguments(_args "${_opts}" "${_single_opts}" "${_multi_opts}" ${ARGN})
unset(SPHINX_BREATHE_PROJECTS)
if(NOT _args_BUILDER)
message(FATAL_ERROR "Sphinx builder not specified for target ${_target}")
elseif(NOT _args_SOURCE_DIRECTORY)
message(FATAL_ERROR "Sphinx source directory not specified for target ${_target}")
else()
if(NOT IS_ABSOLUTE "${_args_SOURCE_DIRECTORY}")
get_filename_component(_sourcedir "${_args_SOURCE_DIRECTORY}" ABSOLUTE)
else()
set(_sourcedir "${_args_SOURCE_DIRECTORY}")
endif()
if(NOT IS_DIRECTORY "${_sourcedir}")
message(FATAL_ERROR "Sphinx source directory '${_sourcedir}' for"
"target ${_target} does not exist")
endif()
endif()
set(_builder "${_args_BUILDER}")
if(_args_OUTPUT_DIRECTORY)
set(_outputdir "${_args_OUTPUT_DIRECTORY}")
else()
set(_outputdir "${CMAKE_CURRENT_BINARY_DIR}/${_target}")
endif()
if(_args_BREATHE_PROJECTS)
if(NOT Sphinx_breathe_FOUND)
message(FATAL_ERROR "Sphinx extension 'breathe' is not available. Needed"
"by sphinx_add_docs for target ${_target}")
endif()
list(APPEND SPHINX_EXTENSIONS breathe)
foreach(_doxygen_target ${_args_BREATHE_PROJECTS})
if(TARGET ${_doxygen_target})
list(APPEND _depends ${_doxygen_target})
# Doxygen targets are supported. Verify that a Doxyfile exists.
get_target_property(_dir ${_doxygen_target} BINARY_DIR)
set(_doxyfile "${_dir}/Doxyfile.${_doxygen_target}")
if(NOT EXISTS "${_doxyfile}")
message(FATAL_ERROR "Target ${_doxygen_target} is not a Doxygen"
"target, needed by sphinx_add_docs for target"
"${_target}")
endif()
# Read the Doxyfile, verify XML generation is enabled and retrieve the
# output directory.
file(READ "${_doxyfile}" _contents)
if(NOT _contents MATCHES "GENERATE_XML *= *YES")
message(FATAL_ERROR "Doxygen target ${_doxygen_target} does not"
"generate XML, needed by sphinx_add_docs for"
"target ${_target}")
elseif(_contents MATCHES "OUTPUT_DIRECTORY *= *([^ ][^\n]*)")
string(STRIP "${CMAKE_MATCH_1}" _dir)
set(_name "${_doxygen_target}")
set(_dir "${_dir}/xml")
else()
message(FATAL_ERROR "Cannot parse Doxyfile generated by Doxygen"
"target ${_doxygen_target}, needed by"
"sphinx_add_docs for target ${_target}")
endif()
elseif(_doxygen_target MATCHES "([^: ]+) *: *(.*)")
set(_name "${CMAKE_MATCH_1}")
string(STRIP "${CMAKE_MATCH_2}" _dir)
endif()
if(_name AND _dir)
if(_breathe_projects)
set(_breathe_projects "${_breathe_projects}, \"${_name}\": \"${_dir}\"")
else()
set(_breathe_projects "\"${_name}\": \"${_dir}\"")
endif()
if(NOT _breathe_default_project)
set(_breathe_default_project "${_name}")
endif()
endif()
endforeach()
endif()
set(_cachedir "${CMAKE_CURRENT_BINARY_DIR}/${_target}.cache")
file(MAKE_DIRECTORY "${_cachedir}")
if(EXISTS "${_sourcedir}/_static")
file(COPY "${_sourcedir}/_static" DESTINATION "${_cachedir}")
endif()
if(EXISTS "${_sourcedir}/_templates")
file(COPY "${_sourcedir}/_templates" DESTINATION "${_cachedir}")
endif()
if(EXISTS "${_sourcedir}/conf.py")
configure_file("${_sourcedir}/conf.py" "${_cachedir}/conf.py" @ONLY)
else()
_Sphinx_generate_confpy(${_target} "${_cachedir}")
endif()
if(_breathe_projects)
file(APPEND "${_cachedir}/conf.py"
"\nbreathe_projects = { ${_breathe_projects} }"
"\nbreathe_default_project = '${_breathe_default_project}'")
endif()
string(REPLACE " " ";" _Sphinx_executable ${SPHINX_BUILD_EXECUTABLE})
add_custom_target(
${_target} ALL
COMMAND ${_Sphinx_executable}
-b ${_builder}
-d "${CMAKE_CURRENT_BINARY_DIR}/${_target}.cache/_doctrees"
-c "${CMAKE_CURRENT_BINARY_DIR}/${_target}.cache"
"${_sourcedir}"
"${_outputdir}"
COMMENT "Building ${_target} Sphinx document"
DEPENDS ${_depends})
unset(_Sphinx_executable)
endfunction()