diff --git a/.gitignore b/.gitignore index 73358ad..623b582 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Build artifacts from generating cmake files +cmake/build + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/README.md b/README.md index 6dfcd1a..5623077 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,43 @@ py::class_(m, "MyClass", DOC(MyClass)) ... ``` +### CMake +The `pybind11_mkdoc` CMake function is included to easily generate header for a pybind11 module when +compiling said module in CMake. The function generates the headers based on the arguments provided. +In addition, it add target dependencies so the pybind11-mkdoc header file is generated before +the pybind11 module. Also, it will automatically add the current binary directory to the pybind11 +module's includes, so it can easily be included when compiling the module. + +The required parameters are: +* OUTPUT - The name of the output file. +* PYBIND11_MODULE - The pybind11 module target that these docs will be used for. +* HEADERS - The header files to create docs for. These can be absoulte paths or relative to the + current source directory. + +The optional parameters are: +* EXTRA_ARGS - This string argument will be added verbatim to the pybind11-mkdoc command. + +Below is an exmaple of how it is used: +```cmake +# Find pybind11-mkdoc +# This assumes you have already run a find_package for Python. +execute_process( + COMMAND ${Python_EXECUTABLE} -c "import pybind11_mkdoc; print(pybind11_mkdoc.get_cmake_dir())" + OUTPUT_VARIABLE pybind11_mkdoc_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +find_package(pybind11_mkdoc REQUIRED CONFIG) + +# Add the pybind11 module +pybind11_add_module(my_pybind11_module my_src_files.cc) +pybind11_mkdoc( + OUTPUT my_pybind11_module_docs.h + PYBIND11_MODULE my_pybind11_module + HEADERS + header_1.h + /absolute/path/to/header_2.h +) +``` ## Limitations This tool supports Linux and macOS for Python versions 3.8 to 3.11. Also, it diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt new file mode 100644 index 0000000..20761ef --- /dev/null +++ b/cmake/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.25) +project(pybind11_mkdoc) + +include(CMakePackageConfigHelpers) + +# Generate version file +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/pybind11_mkdocConfigVersion.cmake" + VERSION 2.6.2 + COMPATIBILITY SameMajorVersion +) + +# Create config file +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/pybind11_mkdocConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/pybind11_mkdocConfig.cmake" + INSTALL_DESTINATION pybind11_mkdoc +) + +# Install config + version +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/pybind11_mkdocConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/pybind11_mkdocConfigVersion.cmake" + DESTINATION pybind11_mkdoc +) + +# Install functions +install(FILES + "${CMAKE_CURRENT_SOURCE_DIR}/pybind11_mkdoc_functions.cmake" + DESTINATION pybind11_mkdoc +) diff --git a/cmake/Makefile b/cmake/Makefile new file mode 100644 index 0000000..c630ec2 --- /dev/null +++ b/cmake/Makefile @@ -0,0 +1,10 @@ +.PHONY: all + +all : ../pybind11_mkdoc/share/cmake/pybind11_mkdoc + +build: CMakeLists.txt + cmake -B build -S . -DCMAKE_INSTALL_PREFIX=../pybind11_mkdoc/share/cmake + +../pybind11_mkdoc/share/cmake/pybind11_mkdoc: pybind11_mkdocConfig.cmake.in pybind11_mkdoc_functions.cmake build + cmake --build build + cmake --install build diff --git a/cmake/pybind11_mkdocConfig.cmake.in b/cmake/pybind11_mkdocConfig.cmake.in new file mode 100644 index 0000000..6f135e7 --- /dev/null +++ b/cmake/pybind11_mkdocConfig.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/pybind11_mkdoc_functions.cmake") + +# If you export targets: +# include("${CMAKE_CURRENT_LIST_DIR}/my_pkgTargets.cmake") diff --git a/cmake/pybind11_mkdoc_functions.cmake b/cmake/pybind11_mkdoc_functions.cmake new file mode 100644 index 0000000..4cc444c --- /dev/null +++ b/cmake/pybind11_mkdoc_functions.cmake @@ -0,0 +1,68 @@ +# This function is used to run pybind11-mkdoc for the headers of a pybind11 module. +# In addition, this will also add target dependencies so the pybind11-mkdoc header +# file is generated before the pybind11 module. Also, this will automatically add +# the current binary directory to the pybind11 module's includes, so it can +# easily be included when compiling the module. +# +# The required parameters are: +# * OUTPUT - The name of the output file. +# * PYBIND11_MODULE - The pybind11 module target that these docs will be used for. +# * HEADERS - The header files to create docs for. These can be absoulte paths or relative to the +# current source directory. +# +# The optional parameters are: +# * EXTRA_ARGS - This string argument will be added verbatim to the pybind11-mkdoc command. +# +# Example usage: +# pybind11_add_module(my_pybind11_module src/my_pybind11_module.cc) +# pybind11_mkdoc( +# OUTPUT my_pybind11_module_doc.h +# PYBIND11_MODULE my_pybind11_module +# HEADERS +# include/my_header_1.h +# /absolute/path/to/header.h +# ) +function (pybind11_mkdoc) + set(options) + set(oneValueArgs OUTPUT PYBIND11_MODULE EXTRA_ARGS) + set(multiValueArgs HEADERS) + cmake_parse_arguments(PARSE_ARGV 0 arg + "${options}" "${oneValueArgs}" "${multiValueArgs}" + ) + + # Include directories for the pybind11 module + set(prop "$") + + # Remove the header file from the list + set(HEADERS "") + # Run through all the other arguments. + foreach(header ${arg_HEADERS}) + if(IS_ABSOLUTE ${header}) + # If it is an absolute path, then add it as is. + list(APPEND HEADERS ${header}) + else() + # Otherwise, assume it is relative to the current source directory. + list(APPEND HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/${header}") + endif() + endforeach() + + # Add a custom target and command for the full header file location that runs pybind11-mkdoc + # We automatically include the source directory and build/include + add_custom_target( + pybind11_mkdoc_${arg_OUTPUT} + DEPENDS ${arg_OUTPUT} + ) + + add_custom_command( + OUTPUT ${arg_OUTPUT} + COMMAND ${Python_EXECUTABLE} -m pybind11_mkdoc ${arg_EXTRA_ARGS} -o ${arg_OUTPUT} "$<$:-I$>" ${HEADERS} + DEPENDS ${HEADERS} + COMMAND_EXPAND_LISTS + ) + + # Add a dependency so that the pybind11-mkdoc command runs before we try to compile the pybind11 module + add_dependencies(${arg_PYBIND11_MODULE} pybind11_mkdoc_${arg_OUTPUT}) + + # Add the current binary directory to the pybind11 module so it can be included easily + target_include_directories(${arg_PYBIND11_MODULE} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +endfunction() diff --git a/pybind11_mkdoc/__init__.py b/pybind11_mkdoc/__init__.py index 567d266..7dd1475 100644 --- a/pybind11_mkdoc/__init__.py +++ b/pybind11_mkdoc/__init__.py @@ -7,6 +7,7 @@ import argparse import os import re +from pathlib import Path from pybind11_mkdoc.mkdoc_lib import mkdoc @@ -73,6 +74,18 @@ def _append_definition(args: list, definition: str): pass +def get_cmake_dir() -> str: + """ + Return the path to the pybind11_mkdoc CMake module directory. + """ + cmake_installed_path = Path(__file__).parent / "share" / "cmake" / "pybind11_mkdoc" + if cmake_installed_path.exists(): + return str(cmake_installed_path) + + msg = "pybind11_mkdoc cmake files not found." + raise ImportError(msg) + + def main(): """ Entry point for the `pybind11_mkdoc` console script. diff --git a/pybind11_mkdoc/mkdoc_lib.py b/pybind11_mkdoc/mkdoc_lib.py index 6f57054..23c9208 100755 --- a/pybind11_mkdoc/mkdoc_lib.py +++ b/pybind11_mkdoc/mkdoc_lib.py @@ -173,7 +173,7 @@ def process_comment(comment): rm_lines.append(k) add_to = (t_params, name) elif m := return_re.match(line): - text, = m.groups() + (text,) = m.groups() ret.append(text.strip()) add_to = (ret, len(ret) - 1) rm_lines.append(k) diff --git a/pybind11_mkdoc/share/cmake/pybind11_mkdoc/pybind11_mkdocConfig.cmake b/pybind11_mkdoc/share/cmake/pybind11_mkdoc/pybind11_mkdocConfig.cmake new file mode 100644 index 0000000..c72741b --- /dev/null +++ b/pybind11_mkdoc/share/cmake/pybind11_mkdoc/pybind11_mkdocConfig.cmake @@ -0,0 +1,30 @@ + +####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() ####### +####### Any changes to this file will be overwritten by the next CMake run #### +####### The input file was pybind11_mkdocConfig.cmake.in ######## + +get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../" ABSOLUTE) + +macro(set_and_check _var _file) + set(${_var} "${_file}") + if(NOT EXISTS "${_file}") + message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !") + endif() +endmacro() + +macro(check_required_components _NAME) + foreach(comp ${${_NAME}_FIND_COMPONENTS}) + if(NOT ${_NAME}_${comp}_FOUND) + if(${_NAME}_FIND_REQUIRED_${comp}) + set(${_NAME}_FOUND FALSE) + endif() + endif() + endforeach() +endmacro() + +#################################################################################### + +include("${CMAKE_CURRENT_LIST_DIR}/pybind11_mkdoc_functions.cmake") + +# If you export targets: +# include("${CMAKE_CURRENT_LIST_DIR}/my_pkgTargets.cmake") diff --git a/pybind11_mkdoc/share/cmake/pybind11_mkdoc/pybind11_mkdocConfigVersion.cmake b/pybind11_mkdoc/share/cmake/pybind11_mkdoc/pybind11_mkdocConfigVersion.cmake new file mode 100644 index 0000000..df97cb6 --- /dev/null +++ b/pybind11_mkdoc/share/cmake/pybind11_mkdoc/pybind11_mkdocConfigVersion.cmake @@ -0,0 +1,65 @@ +# This is a basic version file for the Config-mode of find_package(). +# It is used by write_basic_package_version_file() as input file for configure_file() +# to create a version-file which can be installed along a config.cmake file. +# +# The created file sets PACKAGE_VERSION_EXACT if the current version string and +# the requested version string are exactly the same and it sets +# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version, +# but only if the requested major version is the same as the current one. +# The variable CVF_VERSION must be set before calling configure_file(). + + +set(PACKAGE_VERSION "2.6.2") + +if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + + if("2.6.2" MATCHES "^([0-9]+)\\.") + set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}") + if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0) + string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}") + endif() + else() + set(CVF_VERSION_MAJOR "2.6.2") + endif() + + if(PACKAGE_FIND_VERSION_RANGE) + # both endpoints of the range must have the expected major version + math (EXPR CVF_VERSION_MAJOR_NEXT "${CVF_VERSION_MAJOR} + 1") + if (NOT PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR + OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX_MAJOR STREQUAL CVF_VERSION_MAJOR) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX VERSION_LESS_EQUAL CVF_VERSION_MAJOR_NEXT))) + set(PACKAGE_VERSION_COMPATIBLE FALSE) + elseif(PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR + AND ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS_EQUAL PACKAGE_FIND_VERSION_MAX) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MAX))) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + else() + if(PACKAGE_FIND_VERSION_MAJOR STREQUAL CVF_VERSION_MAJOR) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + + if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() + endif() +endif() + + +# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: +if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "") + return() +endif() + +# check that the installed version has the same 32/64bit-ness as the one which is currently searching: +if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") + math(EXPR installedBits "8 * 8") + set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") + set(PACKAGE_VERSION_UNSUITABLE TRUE) +endif() diff --git a/pybind11_mkdoc/share/cmake/pybind11_mkdoc/pybind11_mkdoc_functions.cmake b/pybind11_mkdoc/share/cmake/pybind11_mkdoc/pybind11_mkdoc_functions.cmake new file mode 100644 index 0000000..3b6f7b0 --- /dev/null +++ b/pybind11_mkdoc/share/cmake/pybind11_mkdoc/pybind11_mkdoc_functions.cmake @@ -0,0 +1,67 @@ +# This function is used to run pybind11-mkdoc for the headers of a pybind11 module. +# In addition, this will also add target dependencies so the pybind11-mkdoc header +# file is generated before the pybind11 module. Also, this will automatically add +# the current binary directory to the pybind11 module's includes, so it can +# easily be included when compiling the module. +# +# The required parameters are: +# * OUTPUT - The name of the output file. +# * PYBIND11_MODULE - The pybind11 module target that these docs will be used for. +# * HEADERS - The header files to create docs for. These can be absoulte paths or relative to the +# current source directory. +# +# The optional parameters are: +# * EXTRA_ARGS - This string argument will be added verbatim to the pybind11-mkdoc command. +# +# Example usage: +# pybind11_add_module(my_pybind11_module src/my_pybind11_module.cc) +# pybind11_mkdoc( +# OUTPUT my_pybind11_module_doc.h +# PYBIND11_MODULE my_pybind11_module +# HEADERS +# include/my_header_1.h +# /absolute/path/to/header.h +# ) +function (pybind11_mkdoc) + set(options) + set(oneValueArgs OUTPUT PYBIND11_MODULE EXTRA_ARGS) + set(multiValueArgs HEADERS) + cmake_parse_arguments(PARSE_ARGV 0 arg + "${options}" "${oneValueArgs}" "${multiValueArgs}" + ) + + # Include directories for the pybind11 module + set(prop "$") + + # Remove the header file from the list + set(HEADERS "") + # Run through all the other arguments. + foreach(header ${arg_HEADERS}) + if(IS_ABSOLUTE ${header}) + # If it is an absolute path, then add it as is. + list(APPEND HEADERS ${header}) + else() + # Otherwise, assume it is relative to the current source directory. + list(APPEND HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/${header}") + endif() + endforeach() + + # Add a custom target and command for the full header file location that runs pybind11-mkdoc + # We automatically include the source directory and build/include + add_custom_target( + pybind11_mkdoc_${arg_OUTPUT} + DEPENDS ${arg_OUTPUT} + ) + + add_custom_command( + OUTPUT ${arg_OUTPUT} + COMMAND ${Python_EXECUTABLE} -m pybind11_mkdoc ${arg_EXTRA_ARGS} -o ${arg_OUTPUT} "$<$:-I$>" ${HEADERS} + DEPENDS ${HEADERS} + COMMAND_EXPAND_LISTS + ) + + add_dependencies(${arg_PYBIND11_MODULE} pybind11_mkdoc_${arg_OUTPUT}) + + # Add the current binary directory to the pybind11 module so it can be included easily + target_include_directories(${arg_PYBIND11_MODULE} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +endfunction() diff --git a/tests/cmake_docs/CMakeLists.txt b/tests/cmake_docs/CMakeLists.txt new file mode 100644 index 0000000..d77e3e6 --- /dev/null +++ b/tests/cmake_docs/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.25) +project(sbox) + +# Find Python +find_package(Python REQUIRED COMPONENTS Interpreter Development) + +# Find pybind11 +execute_process( + COMMAND ${Python_EXECUTABLE} -c "import pybind11; print(pybind11.get_cmake_dir())" + OUTPUT_VARIABLE pybind11_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +# This is needed for CMake < 3.27. After Cmake 3.27+, can remove setting PYBIND11_FINDPYTHON. +set(PYBIND11_FINDPYTHON ON) +find_package(pybind11 3.0 REQUIRED CONFIG) + +# Find pybind11-mkdoc +execute_process( + COMMAND ${Python_EXECUTABLE} -c "import pybind11_mkdoc; print(pybind11_mkdoc.get_cmake_dir())" + OUTPUT_VARIABLE pybind11_mkdoc_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +find_package(pybind11_mkdoc REQUIRED CONFIG) + +# Add the pybind11 module +pybind11_add_module(my_pybind11_module dummy.cc) +target_include_directories(my_pybind11_module PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../sample_header_docs) +pybind11_mkdoc( + OUTPUT my_module_docs.h + PYBIND11_MODULE my_pybind11_module + EXTRA_ARGS $ENV{PYBIND11_TEST_EXTRA_ARGS} + HEADERS + "../sample_header_docs/sample header with spaces.h" + ${CMAKE_CURRENT_SOURCE_DIR}/../sample_header_docs/sample_header_2.h +) diff --git a/tests/cmake_docs/dummy.cc b/tests/cmake_docs/dummy.cc new file mode 100644 index 0000000..96f30e0 --- /dev/null +++ b/tests/cmake_docs/dummy.cc @@ -0,0 +1,13 @@ +#include "sample header with spaces.h" +#include "sample_header_2.h" +#include "my_module_docs.h" +#include + +// This is not a real pybind11 module. It is just some dummy code so we can test out +// running pybind11-mkdoc. + +int method1(std::vector, std::map) {return 0;} + +void method2(int p1, int p2){ + std::cout << DOC(Base, method2) << std::endl; +} diff --git a/tests/cmake_docs/my_module_docs_extra_truth.h b/tests/cmake_docs/my_module_docs_extra_truth.h new file mode 100644 index 0000000..5783880 --- /dev/null +++ b/tests/cmake_docs/my_module_docs_extra_truth.h @@ -0,0 +1,83 @@ +/* + This file contains docstrings for use in the Python bindings. + Do not edit! They were automatically extracted by pybind11_mkdoc. + */ + +#define MKD_EXPAND(x) x +#define MKD_COUNT(_1, _2, _3, _4, _5, _6, _7, COUNT, ...) COUNT +#define MKD_VA_SIZE(...) MKD_EXPAND(MKD_COUNT(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)) +#define MKD_CAT1(a, b) a ## b +#define MKD_CAT2(a, b) MKD_CAT1(a, b) +#define MKD_DOC1(n1) mkd_doc_##n1 +#define MKD_DOC2(n1, n2) mkd_doc_##n1##_##n2 +#define MKD_DOC3(n1, n2, n3) mkd_doc_##n1##_##n2##_##n3 +#define MKD_DOC4(n1, n2, n3, n4) mkd_doc_##n1##_##n2##_##n3##_##n4 +#define MKD_DOC5(n1, n2, n3, n4, n5) mkd_doc_##n1##_##n2##_##n3##_##n4##_##n5 +#define MKD_DOC6(n1, n2, n3, n4, n5, n6) mkd_doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6 +#define MKD_DOC7(n1, n2, n3, n4, n5, n6, n7) mkd_doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6##_##n7 +#define DOC(...) MKD_EXPAND(MKD_EXPAND(MKD_CAT2(MKD_DOC, MKD_VA_SIZE(__VA_ARGS__)))(__VA_ARGS__)) + +#if defined(__GNUG__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + + +static const char *mkd_doc_Base = R"doc(A simple base class.)doc"; + +static const char *mkd_doc_Base_method1 = +R"doc(Description for method1. + +This is the extended description for method1. + +Args: + p1: I am the first parameter. + p2: I am the second parameter. + +Returns: + An integer is what I return. + +Raises: + runtime_error: Throws runtime error if p1 is empty.)doc"; + +static const char *mkd_doc_Base_method2 = +R"doc(Description for method1. + +This is the extended description for method1. + +Args: + p1: I am a very long description for parameter 1. Let's ensure + that this gets wrapped properly. + p2: I am a very long description for parameter 2. However, I'm + broken out onto two lines. Will this be parsed correctly? + +Returns: + An integer is what I return. + +Raises: + runtime_error: Throws runtime error if p1 is 0. + invalid_argument: Throws invalid_argument error if p2 is 0.)doc"; + +static const char *mkd_doc_Base_method3 = R"doc(A method that is only included if MY_EXTRA_DEFINE is defined.)doc"; + +static const char *mkd_doc_RootLevelSymbol = +R"doc(Root-level symbol. Magna fermentum iaculis eu non diam phasellus +vestibulum.)doc"; + +static const char *mkd_doc_drake_MidLevelSymbol = +R"doc(1. Begin first ordered list element. Rutrum quisque non tellus orci ac + auctor. End first ordered list element. +2. Begin second ordered list element. Ipsum faucibus vitae aliquet + nec. Ligula ullamcorper malesuada proin libero. End second ordered + list element. +3. Begin third ordered list element. Dictum sit amet justo donec enim. + Pharetra convallis posuere morbi leo urna molestie. End third + ordered list element. + +Senectus et netus et malesuada fames ac. Tincidunt lobortis feugiat +vivamus at augue eget arcu dictum varius.)doc"; + +#if defined(__GNUG__) +#pragma GCC diagnostic pop +#endif + diff --git a/tests/cmake_docs/my_module_docs_truth.h b/tests/cmake_docs/my_module_docs_truth.h new file mode 100644 index 0000000..2742075 --- /dev/null +++ b/tests/cmake_docs/my_module_docs_truth.h @@ -0,0 +1,81 @@ +/* + This file contains docstrings for use in the Python bindings. + Do not edit! They were automatically extracted by pybind11_mkdoc. + */ + +#define MKD_EXPAND(x) x +#define MKD_COUNT(_1, _2, _3, _4, _5, _6, _7, COUNT, ...) COUNT +#define MKD_VA_SIZE(...) MKD_EXPAND(MKD_COUNT(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)) +#define MKD_CAT1(a, b) a ## b +#define MKD_CAT2(a, b) MKD_CAT1(a, b) +#define MKD_DOC1(n1) mkd_doc_##n1 +#define MKD_DOC2(n1, n2) mkd_doc_##n1##_##n2 +#define MKD_DOC3(n1, n2, n3) mkd_doc_##n1##_##n2##_##n3 +#define MKD_DOC4(n1, n2, n3, n4) mkd_doc_##n1##_##n2##_##n3##_##n4 +#define MKD_DOC5(n1, n2, n3, n4, n5) mkd_doc_##n1##_##n2##_##n3##_##n4##_##n5 +#define MKD_DOC6(n1, n2, n3, n4, n5, n6) mkd_doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6 +#define MKD_DOC7(n1, n2, n3, n4, n5, n6, n7) mkd_doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6##_##n7 +#define DOC(...) MKD_EXPAND(MKD_EXPAND(MKD_CAT2(MKD_DOC, MKD_VA_SIZE(__VA_ARGS__)))(__VA_ARGS__)) + +#if defined(__GNUG__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + + +static const char *mkd_doc_Base = R"doc(A simple base class.)doc"; + +static const char *mkd_doc_Base_method1 = +R"doc(Description for method1. + +This is the extended description for method1. + +Args: + p1: I am the first parameter. + p2: I am the second parameter. + +Returns: + An integer is what I return. + +Raises: + runtime_error: Throws runtime error if p1 is empty.)doc"; + +static const char *mkd_doc_Base_method2 = +R"doc(Description for method1. + +This is the extended description for method1. + +Args: + p1: I am a very long description for parameter 1. Let's ensure + that this gets wrapped properly. + p2: I am a very long description for parameter 2. However, I'm + broken out onto two lines. Will this be parsed correctly? + +Returns: + An integer is what I return. + +Raises: + runtime_error: Throws runtime error if p1 is 0. + invalid_argument: Throws invalid_argument error if p2 is 0.)doc"; + +static const char *mkd_doc_RootLevelSymbol = +R"doc(Root-level symbol. Magna fermentum iaculis eu non diam phasellus +vestibulum.)doc"; + +static const char *mkd_doc_drake_MidLevelSymbol = +R"doc(1. Begin first ordered list element. Rutrum quisque non tellus orci ac + auctor. End first ordered list element. +2. Begin second ordered list element. Ipsum faucibus vitae aliquet + nec. Ligula ullamcorper malesuada proin libero. End second ordered + list element. +3. Begin third ordered list element. Dictum sit amet justo donec enim. + Pharetra convallis posuere morbi leo urna molestie. End third + ordered list element. + +Senectus et netus et malesuada fames ac. Tincidunt lobortis feugiat +vivamus at augue eget arcu dictum varius.)doc"; + +#if defined(__GNUG__) +#pragma GCC diagnostic pop +#endif + diff --git a/tests/cmake_test.py b/tests/cmake_test.py new file mode 100644 index 0000000..5d1d616 --- /dev/null +++ b/tests/cmake_test.py @@ -0,0 +1,39 @@ +import os +import sys +import subprocess +from pathlib import Path + +DIR = Path(__file__).resolve().parent + + +def test_pybind11_mkdoc_cmake(tmp_path: Path) -> None: + # Run pybind11-mkdoc and put the output in a temp file + build_dir = tmp_path / "build" + subprocess.run(["cmake", "-B", build_dir, "-S", DIR / "cmake_docs", f"-DPython_EXECUTABLE={sys.executable}"], check=True) + subprocess.run(["cmake", "--build", build_dir], check=True) + + # Ensure the header file matches + res = (build_dir / "my_module_docs.h").read_text(encoding="utf-8") + + with open(DIR / "cmake_docs" / "my_module_docs_truth.h") as f: + expected = f.read() + + assert res == expected + + +def test_pybind11_mkdoc_cmake_extra_args(tmp_path: Path) -> None: + # Run pybind11-mkdoc and put the output in a temp file + build_dir = tmp_path / "build" + env = os.environ.copy() + env["PYBIND11_TEST_EXTRA_ARGS"] = "-DMY_EXTRA_DEFINE=1" + + subprocess.run(["cmake", "-B", build_dir, "-S", DIR / "cmake_docs", f"-DPython_EXECUTABLE={sys.executable}"], check=True, env=env) + subprocess.run(["cmake", "--build", build_dir], check=True, env=env) + + # Ensure the header file matches + res = (build_dir / "my_module_docs.h").read_text(encoding="utf-8") + + with open(DIR / "cmake_docs" / "my_module_docs_extra_truth.h") as f: + expected = f.read() + + assert res == expected diff --git a/tests/sample_header_docs/sample_header_2.h b/tests/sample_header_docs/sample_header_2.h index c62cb80..a7e9fab 100644 --- a/tests/sample_header_docs/sample_header_2.h +++ b/tests/sample_header_docs/sample_header_2.h @@ -38,4 +38,11 @@ class Base { * @exception invalid_argument Throws invalid_argument error if p2 is 0. */ void method2(int p1, int p2); + +#ifdef MY_EXTRA_DEFINE + /** + * @brief A method that is only included if MY_EXTRA_DEFINE is defined. + */ + void method3(); +#endif };