diff --git a/dbc_gen_cpp/cmake/generate_dbc_cpp.cmake b/dbc_gen_cpp/cmake/generate_dbc_cpp.cmake index 9ea17d5..e340ca5 100644 --- a/dbc_gen_cpp/cmake/generate_dbc_cpp.cmake +++ b/dbc_gen_cpp/cmake/generate_dbc_cpp.cmake @@ -1,57 +1,84 @@ # SPDX-FileCopyrightText: 2026 Polymath Robotics, Inc. # SPDX-License-Identifier: Apache-2.0 -function(generate_dbc_cpp library_name) +# +# generate_dbc_cpp( DBC ) +# +# Generates C/C++ CAN message types from a DBC file and builds them into a static +# library named (C++ namespace and C-symbol prefix also , +# headers at include//.hpp). +# +# The library is ALWAYS installed and exported, so a downstream package that does +# find_package() can link it as the imported target +# ::. Within the generating package it is also usable by +# its plain local name. +# +# NOTE: this is a macro, not a function, on purpose. It calls ament_export_targets() +# and ament_export_dependencies(), whose bookkeeping is stored in ordinary variables +# in the current scope; ament_package() reads them from the package's top-level scope, +# so they must not be trapped inside a function scope. +macro(generate_dbc_cpp library_name) find_package(Python3 REQUIRED COMPONENTS Interpreter) - set(one_value_args DBC) - cmake_parse_arguments(ARG "" "${one_value_args}" "" ${ARGN}) - if(NOT ARG_DBC) + cmake_parse_arguments(_dbc_arg "" "DBC" "" ${ARGN}) + if(NOT _dbc_arg_DBC) message(FATAL_ERROR "generate_dbc_cpp: Missing required keyword argument DBC") endif() - set(gen_basedir ${CMAKE_CURRENT_BINARY_DIR}/dbc_gen_cpp) - set(gen_dir ${gen_basedir}/${library_name}) + set(_dbc_gen_basedir ${CMAKE_CURRENT_BINARY_DIR}/dbc_gen_cpp) + set(_dbc_gen_dir ${_dbc_gen_basedir}/${library_name}) - set(generated_c ${gen_dir}/${library_name}.c) - set(generated_h ${gen_dir}/${library_name}.h) - set(generated_hpp ${gen_dir}/${library_name}.hpp) - set(generated_files ${generated_c} ${generated_h} ${generated_hpp}) + set(_dbc_generated_c ${_dbc_gen_dir}/${library_name}.c) + set(_dbc_generated_h ${_dbc_gen_dir}/${library_name}.h) + set(_dbc_generated_hpp ${_dbc_gen_dir}/${library_name}.hpp) - # Kind of awkward, but makes the sources get regenerated if the generator tool changes, by depending on the generator sources. - # Finds the python module directory via Python3 import + # Kind of awkward, but makes the sources get regenerated if the generator tool + # changes, by depending on the generator sources. Finds the python module directory. execute_process( COMMAND ${Python3_EXECUTABLE} -c "import dbc_gen_cpp, os; print(os.path.dirname(dbc_gen_cpp.__file__))" - OUTPUT_VARIABLE GENERATOR_DIR + OUTPUT_VARIABLE _dbc_generator_dir OUTPUT_STRIP_TRAILING_WHITESPACE ) - file(GLOB_RECURSE GENERATOR_SOURCES CONFIGURE_DEPENDS - "${GENERATOR_DIR}/*.py" - "${GENERATOR_DIR}/templates/*.j2" + file(GLOB_RECURSE _dbc_generator_sources CONFIGURE_DEPENDS + "${_dbc_generator_dir}/*.py" + "${_dbc_generator_dir}/templates/*.j2" ) - # Generate the source files + # Generate the source files. add_custom_command( - OUTPUT ${generated_files} - COMMAND ${Python3_EXECUTABLE} -m dbc_gen_cpp ${ARG_DBC} -o ${gen_dir} -n ${library_name} - DEPENDS ${ARG_DBC} ${GENERATOR_SOURCES} - COMMENT "Generating C source from DBC ${ARG_DBC} with cantools" + OUTPUT ${_dbc_generated_c} ${_dbc_generated_h} ${_dbc_generated_hpp} + COMMAND ${Python3_EXECUTABLE} -m dbc_gen_cpp ${_dbc_arg_DBC} -o ${_dbc_gen_dir} -n ${library_name} + DEPENDS ${_dbc_arg_DBC} ${_dbc_generator_sources} + COMMENT "Generating C source from DBC ${_dbc_arg_DBC} with cantools" VERBATIM ) add_custom_target(${library_name}_c_sources - DEPENDS ${generated_c} ${generated_h} + DEPENDS ${_dbc_generated_c} ${_dbc_generated_h} ) - # Create the library target from generated sources - add_library(${library_name} STATIC ${generated_c}) + # Create the library target from generated sources. + add_library(${library_name} STATIC ${_dbc_generated_c}) add_dependencies(${library_name} ${library_name}_c_sources) target_link_libraries(${library_name} PUBLIC dbc_gen_cpp::dbc_gen_cpp) target_include_directories(${library_name} - PUBLIC ${gen_basedir} + PUBLIC + $ + $ ) - # Install the generated files to the install space, just in case they're included in public headers + # Install + export the target and its headers so other packages can link it as + # ::${library_name}. install( - FILES ${generated_h} ${generated_hpp} + TARGETS ${library_name} + EXPORT ${library_name}Targets + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include + ) + install( + FILES ${_dbc_generated_h} ${_dbc_generated_hpp} DESTINATION include/${library_name} ) -endfunction() + ament_export_targets(${library_name}Targets HAS_LIBRARY_TARGET) + ament_export_dependencies(dbc_gen_cpp) +endmacro() diff --git a/test_dbc_gen_cpp/CMakeLists.txt b/test_dbc_gen_cpp/CMakeLists.txt index bdcf85b..5426f04 100644 --- a/test_dbc_gen_cpp/CMakeLists.txt +++ b/test_dbc_gen_cpp/CMakeLists.txt @@ -48,6 +48,20 @@ if(BUILD_TESTING) ENV CATCH_CONFIG_CONSOLE_WIDTH=120 WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" ) + + # Verify that generate_dbc_cpp() installs the generated headers into the export + # location (include//) so downstream packages can consume them. + # This exercises the install/export path, which the compiled test above does not: + # that test links the libraries in-tree via their BUILD_INTERFACE include dirs. + ament_add_test( + test_dbc_exported_headers + GENERATE_RESULT_FOR_RETURN_CODE_ZERO + COMMAND ${CMAKE_COMMAND} + -DINSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} + "-DLIBRARIES=fake_vehicle_can;fake_j1939_can" + -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_exported_headers.cmake + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ) endif() ament_package() diff --git a/test_dbc_gen_cpp/test/check_exported_headers.cmake b/test_dbc_gen_cpp/test/check_exported_headers.cmake new file mode 100644 index 0000000..97ef93d --- /dev/null +++ b/test_dbc_gen_cpp/test/check_exported_headers.cmake @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2026 Polymath Robotics, Inc. +# SPDX-License-Identifier: Apache-2.0 +# +# Verifies that generate_dbc_cpp() exports the generated headers into the install +# space so downstream packages can include them as /.hpp. +# +# Invoked with -DINSTALL_PREFIX= -DLIBRARIES=. + +if(NOT DEFINED INSTALL_PREFIX) + message(FATAL_ERROR "check_exported_headers: INSTALL_PREFIX not set") +endif() +if(NOT DEFINED LIBRARIES) + message(FATAL_ERROR "check_exported_headers: LIBRARIES not set") +endif() + +set(_missing "") +foreach(_lib IN LISTS LIBRARIES) + foreach(_ext h hpp) + set(_header "${INSTALL_PREFIX}/include/${_lib}/${_lib}.${_ext}") + if(EXISTS "${_header}") + message(STATUS "Found exported header: ${_header}") + else() + message(WARNING "Missing exported header: ${_header}") + list(APPEND _missing "${_header}") + endif() + endforeach() +endforeach() + +if(_missing) + message(FATAL_ERROR "generate_dbc_cpp did not export the expected headers:\n ${_missing}") +endif()