diff --git a/cmake/nanobind-config.cmake b/cmake/nanobind-config.cmake index ee781859..7edfff62 100644 --- a/cmake/nanobind-config.cmake +++ b/cmake/nanobind-config.cmake @@ -361,7 +361,7 @@ function(nanobind_add_module name) endfunction() function (nanobind_add_stub name) - cmake_parse_arguments(PARSE_ARGV 1 ARG "VERBOSE;INCLUDE_PRIVATE;EXCLUDE_DOCSTRINGS;INSTALL_TIME;EXCLUDE_FROM_ALL" "MODULE;OUTPUT;MARKER_FILE;COMPONENT;PATTERN_FILE" "PYTHON_PATH;DEPENDS") + cmake_parse_arguments(PARSE_ARGV 1 ARG "VERBOSE;INCLUDE_PRIVATE;EXCLUDE_DOCSTRINGS;INSTALL_TIME;EXCLUDE_FROM_ALL" "MODULE;OUTPUT;OUTPUT_DIR;RECURSIVE;MARKER_FILE;COMPONENT;PATTERN_FILE" "PYTHON_PATH;DEPENDS") if (EXISTS ${NB_DIR}/src/stubgen.py) set(NB_STUBGEN "${NB_DIR}/src/stubgen.py") @@ -404,24 +404,34 @@ function (nanobind_add_stub name) list(APPEND NB_STUBGEN_ARGS -m "${ARG_MODULE}") endif() - if (NOT ARG_OUTPUT) - message(FATAL_ERROR "nanobind_add_stub(): an 'OUTPUT' argument must be specified!") - else() + if (ARG_OUTPUT AND ARG_OUTPUT_DIR) + message(FATAL_ERROR "nanobind_add_stub(): 'OUTPUT' and 'OUTPUT_DIR' cannot be used together!") + elseif(ARG_OUTPUT) list(APPEND NB_STUBGEN_ARGS -o "${ARG_OUTPUT}") list(APPEND NB_STUBGEN_OUTPUTS "${ARG_OUTPUT}") + elseif (ARG_OUTPUT_DIR) + list(APPEND NB_STUBGEN_ARGS -O "${ARG_OUTPUT_DIR}") + endif() + + if (ARG_RECURSIVE) + list(APPEND NB_STUBGEN_ARGS -r) endif() set(NB_STUBGEN_CMD "${Python_EXECUTABLE}" "${NB_STUBGEN}" ${NB_STUBGEN_ARGS}) if (NOT ARG_INSTALL_TIME) + set(STUB_FAKE_FILE ${CMAKE_BINARY_DIR}/${name}_stub.tmp) + set(NB_STUBGEN_CMD ${NB_STUBGEN_CMD} "-M" ${STUB_FAKE_FILE}) add_custom_command( - OUTPUT ${NB_STUBGEN_OUTPUTS} + OUTPUT ${NB_STUBGEN_OUTPUTS} ${STUB_FAKE_FILE} COMMAND ${NB_STUBGEN_CMD} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" DEPENDS ${ARG_DEPENDS} "${NB_STUBGEN}" "${ARG_PATTERN_FILE}" ${NB_STUBGEN_EXTRA} ) - add_custom_target(${name} ALL DEPENDS ${NB_STUBGEN_OUTPUTS}) + add_custom_target(${name} ALL DEPENDS ${NB_STUBGEN_OUTPUTS} ${STUB_FAKE_FILE}) + file(GLOB STUB_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/*.pyi" ) + set_target_properties(${name} PROPERTIES ADDITIONAL_CLEAN_FILES "${STUB_FILES}") else() set(NB_STUBGEN_EXTRA "") if (ARG_COMPONENT) diff --git a/docs/api_cmake.rst b/docs/api_cmake.rst index 7832b2b9..6b50aaa6 100644 --- a/docs/api_cmake.rst +++ b/docs/api_cmake.rst @@ -414,7 +414,14 @@ Nanobind's CMake tooling includes a convenience command to interface with the - Specifies the name of the stub file that should be written. The path is relative to ``CMAKE_CURRENT_BINARY_DIR`` for build-time stub generation and relative to ``CMAKE_INSTALL_PREFIX`` for install-time - stub generation. Mandatory. + stub generation. Either ``OUTPUT`` or ``OUTPUT_DIR`` should be specified. + * - ``OUTPUT_DIR`` + - Specifies the directory of the stub files that should be written. The path + is relative to ``CMAKE_CURRENT_BINARY_DIR`` for build-time stub + generation and relative to ``CMAKE_INSTALL_PREFIX`` for install-time + stub generation. Either ``OUTPUT`` or ``OUTPUT_DIR`` should be specified. + * - ``RECURSIVE`` + - Recursively process submodules. Cannot be used with ``OUTPUT``. * - ``PYTHON_PATH`` - List of search paths that should be considered when importing the module. The paths are relative to ``CMAKE_CURRENT_BINARY_DIR`` for diff --git a/src/stubgen.py b/src/stubgen.py index 8a5448c6..7689c87a 100755 --- a/src/stubgen.py +++ b/src/stubgen.py @@ -1277,6 +1277,10 @@ def parse_options(args: List[str]) -> argparse.Namespace: parser.error( "The -o option is not compatible with recursive stub generation (-r)." ) + if opt.output_dir and opt.output_file: + parser.error( + "The output file option (-o) is not compatible with output directory option (-O)." + ) return opt diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7a24ad84..54767358 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -72,7 +72,7 @@ nanobind_add_stub( MODULE py_stub_test OUTPUT ${PYI_PREFIX}py_stub_test.pyi PYTHON_PATH $ - DEPENDS py_stub_test.py + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${PYI_PREFIX}py_stub_test.py ) find_package (Eigen3 3.3.1 NO_MODULE)