Skip to content

Commit 3d391a5

Browse files
authored
Adding cmake functions to generate docs (#51)
1 parent 6fd535f commit 3d391a5

18 files changed

Lines changed: 614 additions & 1 deletion

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Build artifacts from generating cmake files
2+
cmake/build
3+
14
# Byte-compiled / optimized / DLL files
25
__pycache__/
36
*.py[cod]

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,43 @@ py::class_<MyClass>(m, "MyClass", DOC(MyClass))
7474
...
7575
```
7676

77+
### CMake
78+
The `pybind11_mkdoc` CMake function is included to easily generate header for a pybind11 module when
79+
compiling said module in CMake. The function generates the headers based on the arguments provided.
80+
In addition, it add target dependencies so the pybind11-mkdoc header file is generated before
81+
the pybind11 module. Also, it will automatically add the current binary directory to the pybind11
82+
module's includes, so it can easily be included when compiling the module.
83+
84+
The required parameters are:
85+
* OUTPUT - The name of the output file.
86+
* PYBIND11_MODULE - The pybind11 module target that these docs will be used for.
87+
* HEADERS - The header files to create docs for. These can be absoulte paths or relative to the
88+
current source directory.
89+
90+
The optional parameters are:
91+
* EXTRA_ARGS - This string argument will be added verbatim to the pybind11-mkdoc command.
92+
93+
Below is an exmaple of how it is used:
94+
```cmake
95+
# Find pybind11-mkdoc
96+
# This assumes you have already run a find_package for Python.
97+
execute_process(
98+
COMMAND ${Python_EXECUTABLE} -c "import pybind11_mkdoc; print(pybind11_mkdoc.get_cmake_dir())"
99+
OUTPUT_VARIABLE pybind11_mkdoc_DIR
100+
OUTPUT_STRIP_TRAILING_WHITESPACE
101+
)
102+
find_package(pybind11_mkdoc REQUIRED CONFIG)
103+
104+
# Add the pybind11 module
105+
pybind11_add_module(my_pybind11_module my_src_files.cc)
106+
pybind11_mkdoc(
107+
OUTPUT my_pybind11_module_docs.h
108+
PYBIND11_MODULE my_pybind11_module
109+
HEADERS
110+
header_1.h
111+
/absolute/path/to/header_2.h
112+
)
113+
```
77114
## Limitations
78115

79116
This tool supports Linux and macOS for Python versions 3.8 to 3.11. Also, it

cmake/CMakeLists.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
cmake_minimum_required(VERSION 3.25...4.3)
2+
project(pybind11_mkdoc)
3+
4+
include(CMakePackageConfigHelpers)
5+
6+
# Generate version file
7+
write_basic_package_version_file(
8+
"${CMAKE_CURRENT_BINARY_DIR}/pybind11_mkdocConfigVersion.cmake"
9+
VERSION 2.6.2
10+
COMPATIBILITY SameMajorVersion
11+
)
12+
13+
# Create config file
14+
configure_package_config_file(
15+
"${CMAKE_CURRENT_SOURCE_DIR}/pybind11_mkdocConfig.cmake.in"
16+
"${CMAKE_CURRENT_BINARY_DIR}/pybind11_mkdocConfig.cmake"
17+
INSTALL_DESTINATION pybind11_mkdoc
18+
)
19+
20+
# Install config + version
21+
install(FILES
22+
"${CMAKE_CURRENT_BINARY_DIR}/pybind11_mkdocConfig.cmake"
23+
"${CMAKE_CURRENT_BINARY_DIR}/pybind11_mkdocConfigVersion.cmake"
24+
DESTINATION pybind11_mkdoc
25+
)
26+
27+
# Install functions
28+
install(FILES
29+
"${CMAKE_CURRENT_SOURCE_DIR}/pybind11_mkdoc_functions.cmake"
30+
DESTINATION pybind11_mkdoc
31+
)

cmake/CMakePresets.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"version": 6,
3+
"configurePresets": [
4+
{
5+
"name": "default",
6+
"displayName": "Default configure",
7+
"binaryDir": "build",
8+
"cacheVariables": {
9+
"CMAKE_INSTALL_PREFIX": "../pybind11_mkdoc/share/cmake"
10+
}
11+
}
12+
],
13+
"buildPresets": [
14+
{
15+
"name": "default-install",
16+
"displayName": "Default install",
17+
"configurePreset": "default",
18+
"targets": ["install"]
19+
}
20+
],
21+
"workflowPresets": [
22+
{
23+
"name": "default",
24+
"displayName": "Configure, build and install",
25+
"steps": [
26+
{ "type": "configure", "name": "default" },
27+
{ "type": "build", "name": "default-install" }
28+
]
29+
}
30+
]
31+
}

cmake/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Run
2+
```bash
3+
cmake --workflow default
4+
```
5+
to build and install the cmake files. This should be done whenever `pybind11_mkdoc_functions.cmake` is modified.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@PACKAGE_INIT@
2+
3+
include("${CMAKE_CURRENT_LIST_DIR}/pybind11_mkdoc_functions.cmake")
4+
5+
# If you export targets:
6+
# include("${CMAKE_CURRENT_LIST_DIR}/my_pkgTargets.cmake")
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# This function is used to run pybind11-mkdoc for the headers of a pybind11 module.
2+
# In addition, this will also add target dependencies so the pybind11-mkdoc header
3+
# file is generated before the pybind11 module. Also, this will automatically add
4+
# the current binary directory to the pybind11 module's includes, so it can
5+
# easily be included when compiling the module.
6+
#
7+
# The required parameters are:
8+
# * OUTPUT - The name of the output file.
9+
# * PYBIND11_MODULE - The pybind11 module target that these docs will be used for.
10+
# * HEADERS - The header files to create docs for. These can be absoulte paths or relative to the
11+
# current source directory.
12+
#
13+
# The optional parameters are:
14+
# * EXTRA_ARGS - This string argument will be added verbatim to the pybind11-mkdoc command.
15+
#
16+
# Example usage:
17+
# pybind11_add_module(my_pybind11_module src/my_pybind11_module.cc)
18+
# pybind11_mkdoc(
19+
# OUTPUT my_pybind11_module_doc.h
20+
# PYBIND11_MODULE my_pybind11_module
21+
# HEADERS
22+
# include/my_header_1.h
23+
# /absolute/path/to/header.h
24+
# )
25+
function (pybind11_mkdoc)
26+
set(options)
27+
set(oneValueArgs OUTPUT PYBIND11_MODULE EXTRA_ARGS)
28+
set(multiValueArgs HEADERS)
29+
cmake_parse_arguments(PARSE_ARGV 0 arg
30+
"${options}" "${oneValueArgs}" "${multiValueArgs}"
31+
)
32+
33+
# Include directories for the pybind11 module
34+
set(prop "$<TARGET_PROPERTY:${arg_PYBIND11_MODULE},INCLUDE_DIRECTORIES>")
35+
36+
# Remove the header file from the list
37+
set(HEADERS "")
38+
# Run through all the other arguments.
39+
foreach(header ${arg_HEADERS})
40+
if(IS_ABSOLUTE ${header})
41+
# If it is an absolute path, then add it as is.
42+
list(APPEND HEADERS ${header})
43+
else()
44+
# Otherwise, assume it is relative to the current source directory.
45+
list(APPEND HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/${header}")
46+
endif()
47+
endforeach()
48+
49+
# Add a custom target and command for the full header file location that runs pybind11-mkdoc
50+
# We automatically include the source directory and build/include
51+
add_custom_target(
52+
pybind11_mkdoc_${arg_OUTPUT}
53+
DEPENDS ${arg_OUTPUT}
54+
)
55+
56+
add_custom_command(
57+
OUTPUT ${arg_OUTPUT}
58+
COMMAND ${Python_EXECUTABLE} -m pybind11_mkdoc ${arg_EXTRA_ARGS} -o ${arg_OUTPUT} "$<$<BOOL:${prop}>:-I$<JOIN:${prop},;-I>>" ${HEADERS}
59+
DEPENDS ${HEADERS}
60+
COMMAND_EXPAND_LISTS
61+
)
62+
63+
# Add a dependency so that the pybind11-mkdoc command runs before we try to compile the pybind11 module
64+
add_dependencies(${arg_PYBIND11_MODULE} pybind11_mkdoc_${arg_OUTPUT})
65+
66+
# Add the current binary directory to the pybind11 module so it can be included easily
67+
target_include_directories(${arg_PYBIND11_MODULE} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
68+
endfunction()

pybind11_mkdoc/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import argparse
88
import os
99
import re
10+
from pathlib import Path
1011

1112
from pybind11_mkdoc.mkdoc_lib import mkdoc
1213

@@ -73,6 +74,18 @@ def _append_definition(args: list, definition: str):
7374
pass
7475

7576

77+
def get_cmake_dir() -> Path:
78+
"""
79+
Return the path to the pybind11_mkdoc CMake module directory.
80+
"""
81+
cmake_installed_path = Path(__file__).parent / "share" / "cmake" / "pybind11_mkdoc"
82+
if cmake_installed_path.exists():
83+
return cmake_installed_path
84+
85+
msg = "pybind11_mkdoc cmake files not found."
86+
raise ImportError(msg)
87+
88+
7689
def main():
7790
"""
7891
Entry point for the `pybind11_mkdoc` console script.

pybind11_mkdoc/mkdoc_lib.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def process_comment(comment):
173173
rm_lines.append(k)
174174
add_to = (t_params, name)
175175
elif m := return_re.match(line):
176-
text, = m.groups()
176+
(text,) = m.groups()
177177
ret.append(text.strip())
178178
add_to = (ret, len(ret) - 1)
179179
rm_lines.append(k)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() #######
3+
####### Any changes to this file will be overwritten by the next CMake run ####
4+
####### The input file was pybind11_mkdocConfig.cmake.in ########
5+
6+
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../" ABSOLUTE)
7+
8+
macro(set_and_check _var _file)
9+
set(${_var} "${_file}")
10+
if(NOT EXISTS "${_file}")
11+
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
12+
endif()
13+
endmacro()
14+
15+
macro(check_required_components _NAME)
16+
foreach(comp ${${_NAME}_FIND_COMPONENTS})
17+
if(NOT ${_NAME}_${comp}_FOUND)
18+
if(${_NAME}_FIND_REQUIRED_${comp})
19+
set(${_NAME}_FOUND FALSE)
20+
endif()
21+
endif()
22+
endforeach()
23+
endmacro()
24+
25+
####################################################################################
26+
27+
include("${CMAKE_CURRENT_LIST_DIR}/pybind11_mkdoc_functions.cmake")
28+
29+
# If you export targets:
30+
# include("${CMAKE_CURRENT_LIST_DIR}/my_pkgTargets.cmake")

0 commit comments

Comments
 (0)