Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 55 additions & 28 deletions dbc_gen_cpp/cmake/generate_dbc_cpp.cmake
Original file line number Diff line number Diff line change
@@ -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(<library_name> DBC <path/to/file.dbc>)
#
# Generates C/C++ CAN message types from a DBC file and builds them into a static
# library named <library_name> (C++ namespace and C-symbol prefix also <library_name>,
# headers at include/<library_name>/<library_name>.hpp).
#
# The library is ALWAYS installed and exported, so a downstream package that does
# find_package(<this_package>) can link it as the imported target
# <this_package>::<library_name>. 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
$<BUILD_INTERFACE:${_dbc_gen_basedir}>
$<INSTALL_INTERFACE:include>
)

# 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
# <this_package>::${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()
14 changes: 14 additions & 0 deletions test_dbc_gen_cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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/<library_name>/) 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()
31 changes: 31 additions & 0 deletions test_dbc_gen_cpp/test/check_exported_headers.cmake
Original file line number Diff line number Diff line change
@@ -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 <library_name>/<library_name>.hpp.
#
# Invoked with -DINSTALL_PREFIX=<prefix> -DLIBRARIES=<lib1;lib2>.

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()
Loading