From 3e2ace5e8d127d7995a94f1b296cad9152cb7274 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Tue, 1 Jul 2025 22:56:05 -0700 Subject: Add dependency tracking to the Python code generation This ensures that the Python source files are only generated and copied into the output directory if inputs have changed, rather than being done unconditionally. --- cmake/PythonBindings.cmake | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 cmake/PythonBindings.cmake (limited to 'cmake/PythonBindings.cmake') diff --git a/cmake/PythonBindings.cmake b/cmake/PythonBindings.cmake new file mode 100644 index 00000000..206d2a7e --- /dev/null +++ b/cmake/PythonBindings.cmake @@ -0,0 +1,88 @@ +# CMake function for generating Python bindings +# +# This function: +# 1. Creates a custom command to generate the core and enums Python modules +# 2. Creates a custom command to copy all files to the output directory +# 3. Creates a custom target that depends on all output files +# +# Usage: +# generate_python_bindings( +# TARGET_NAME +# DISPLAY_NAME +# GENERATOR_TARGET +# HEADER_FILE +# [TEMPLATE_FILE ] +# OUTPUT_DIRECTORY +# CORE_OUTPUT_FILE +# ENUMS_OUTPUT_FILE +# [PYTHON_SOURCES ] +# ) +# +function(generate_python_bindings) + set(options) + set(oneValueArgs TARGET_NAME DISPLAY_NAME GENERATOR_TARGET HEADER_FILE TEMPLATE_FILE OUTPUT_DIRECTORY CORE_OUTPUT_FILE ENUMS_OUTPUT_FILE) + set(multiValueArgs PYTHON_SOURCES) + + cmake_parse_arguments(PARSE_ARGV 0 ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}") + + foreach(required_arg TARGET_NAME DISPLAY_NAME GENERATOR_TARGET HEADER_FILE OUTPUT_DIRECTORY CORE_OUTPUT_FILE ENUMS_OUTPUT_FILE) + if(NOT ARGS_${required_arg}) + message(FATAL_ERROR "${required_arg} is required") + endif() + endforeach() + + set(CORE_SOURCE_PATH ${PROJECT_SOURCE_DIR}/${ARGS_CORE_OUTPUT_FILE}) + set(ENUMS_SOURCE_PATH ${PROJECT_SOURCE_DIR}/${ARGS_ENUMS_OUTPUT_FILE}) + + set(GENERATOR_DEPENDS ${ARGS_HEADER_FILE} $) + list(APPEND GENERATOR_DEPENDS ${ARGS_TEMPLATE_FILE}) + + add_custom_command( + OUTPUT ${CORE_SOURCE_PATH} ${ENUMS_SOURCE_PATH} + DEPENDS ${GENERATOR_DEPENDS} + COMMENT "Generating ${ARGS_DISPLAY_NAME} Python Sources" + COMMAND ${CMAKE_COMMAND} -E env ASAN_OPTIONS=detect_leaks=0 $ + ${ARGS_HEADER_FILE} + ${CORE_SOURCE_PATH} + ${ARGS_TEMPLATE_FILE} + ${ENUMS_SOURCE_PATH} + VERBATIM + ) + + set(PYTHON_OUTPUT_FILES) + foreach(SOURCE_FILE ${ARGS_PYTHON_SOURCES}) + cmake_path(RELATIVE_PATH SOURCE_FILE BASE_DIRECTORY ${PROJECT_SOURCE_DIR} OUTPUT_VARIABLE REL_PATH) + list(APPEND PYTHON_OUTPUT_FILES ${ARGS_OUTPUT_DIRECTORY}/${REL_PATH}) + endforeach() + + list(APPEND PYTHON_OUTPUT_FILES ${ARGS_OUTPUT_DIRECTORY}/${ARGS_CORE_OUTPUT_FILE}) + list(APPEND PYTHON_OUTPUT_FILES ${ARGS_OUTPUT_DIRECTORY}/${ARGS_ENUMS_OUTPUT_FILE}) + + set(COPY_DEPENDENCIES ${CORE_SOURCE_PATH} ${ENUMS_SOURCE_PATH}) + list(APPEND COPY_DEPENDENCIES ${ARGS_PYTHON_SOURCES}) + + # Generate a script to copy the generated Python files, preserving their directory structure. + file(GENERATE OUTPUT ${PROJECT_BINARY_DIR}/copy_python_sources.cmake + CONTENT " + foreach(PYTHON_SOURCE ${ARGS_PYTHON_SOURCES}) + cmake_path(RELATIVE_PATH PYTHON_SOURCE BASE_DIRECTORY ${PROJECT_SOURCE_DIR} OUTPUT_VARIABLE OUTPUT_SUBPATH) + cmake_path(REMOVE_FILENAME OUTPUT_SUBPATH) + file(COPY $\{PYTHON_SOURCE\} DESTINATION ${ARGS_OUTPUT_DIRECTORY}/$\{OUTPUT_SUBPATH\}) + endforeach() + " + ) + + add_custom_command( + OUTPUT ${PYTHON_OUTPUT_FILES} + DEPENDS ${COPY_DEPENDENCIES} + COMMENT "Copying ${ARGS_DISPLAY_NAME} Python Sources" + COMMAND ${CMAKE_COMMAND} -E make_directory ${ARGS_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -P ${PROJECT_BINARY_DIR}/copy_python_sources.cmake + COMMAND ${CMAKE_COMMAND} -E copy ${CORE_SOURCE_PATH} ${ARGS_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E copy ${ENUMS_SOURCE_PATH} ${ARGS_OUTPUT_DIRECTORY} + VERBATIM + ) + + # Create target that depends on all output files + add_custom_target(${ARGS_TARGET_NAME} ALL DEPENDS ${PYTHON_OUTPUT_FILES}) +endfunction() -- cgit v1.3.1