cmake_minimum_required(VERSION 3.16...3.27) # Instead of building separate executables (one per snippet), we build a single # "snippets" binary with a dispatcher. Each snippet's `main` is renamed to `_main` # at configure time, and a generated dispatcher routes `argv[1]` to the right function. file(GLOB_RECURSE sources_list CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/all/*.cpp) # Not complete examples (code fragments without main): list(FILTER sources_list EXCLUDE REGEX .*/concepts/static/*) list(FILTER sources_list EXCLUDE REGEX .*/migration/log_tick_enabled.*) list(FILTER sources_list EXCLUDE REGEX .*/migration/transactional_transforms/*) list(FILTER sources_list EXCLUDE REGEX .*/tutorials/custom-application-id.*) list(FILTER sources_list EXCLUDE REGEX .*/tutorials/custom-recording-id.*) list(FILTER sources_list EXCLUDE REGEX .*/tutorials/log-file.*) list(FILTER sources_list EXCLUDE REGEX .*/tutorials/log_line.*) list(FILTER sources_list EXCLUDE REGEX .*/tutorials/quick_start.*) list(FILTER sources_list EXCLUDE REGEX .*/tutorials/timelines_example.*) set(GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated_snippets") file(MAKE_DIRECTORY "${GENERATED_DIR}") set(ALL_GENERATED_SOURCES "") set(FORWARD_DECLARATIONS "") set(DISPATCH_ENTRIES "") set(SNIPPET_NAMES "") foreach(SOURCE_PATH ${sources_list}) get_filename_component(SOURCE_NAME ${SOURCE_PATH} NAME_WLE) if(${SOURCE_NAME} STREQUAL "CMakeFiles") continue() endif() # C++ identifier: replace dashes with underscores. string(REPLACE "-" "_" FUNC_NAME "${SOURCE_NAME}") # Read the original source and validate the entry point signature. file(READ "${SOURCE_PATH}" SOURCE_CONTENT) string(FIND "${SOURCE_CONTENT}" "int main(int argc, char* argv[])" _pos) if(_pos EQUAL -1) message(FATAL_ERROR "Snippet '${SOURCE_PATH}' does not use the expected signature: " "int main(int argc, char* argv[]). " "All snippets must use this exact signature." ) endif() # Rename main -> _main. string(REPLACE "int main(int argc, char* argv[])" "int ${FUNC_NAME}_main(int argc, char* argv[])" SOURCE_CONTENT "${SOURCE_CONTENT}" ) # Make non-main helper functions `static` to avoid duplicate symbols when # multiple snippets define functions with the same name (e.g. `run_main`, `truncated_radians`). # If a new snippet adds a helper function that conflicts, the linker will error with # "duplicate symbol" - add a new REPLACE line here to fix it. string(REPLACE "arrow::Status run_main()" "static arrow::Status run_main()" SOURCE_CONTENT "${SOURCE_CONTENT}") string(REPLACE "float truncated_radians(int deg)" "static float truncated_radians(int deg)" SOURCE_CONTENT "${SOURCE_CONTENT}") # Prepend per-file fixups before the original source: # - #define renames for user types that would otherwise cause ODR violations across snippets # (macros don't expand in string literals, so component name strings are preserved). # Add new defines here if a new snippet introduces a conflicting type name. # - #line restores __FILE__ to the original path so that snippets using # `fs::path(__FILE__).parent_path()` (e.g. encoded_image.cpp) find adjacent assets. string(PREPEND SOURCE_CONTENT "#define CustomPosition3D ${FUNC_NAME}_CustomPosition3D\n" "#define CustomPoints3D ${FUNC_NAME}_CustomPoints3D\n" "#define Confidence ${FUNC_NAME}_Confidence\n" "#line 1 \"${SOURCE_PATH}\"\n" ) # If the last statement before the closing `}` isn't a `return`, add `return 0;`. # This handles cases where `return` only appears in nested blocks (e.g. `if (argc < 2) return 1;`) # but the main function body falls off the end without returning. string(REGEX MATCH "return [^;]*;[ \t\r\n]*}[ \t\r\n]*$" _ends_with_return "${SOURCE_CONTENT}") if(NOT _ends_with_return) string(REGEX REPLACE "}[ \t\r\n]*$" " return 0;\n}\n" SOURCE_CONTENT "${SOURCE_CONTENT}") endif() # Write the transformed source. file(WRITE "${GENERATED_DIR}/${FUNC_NAME}.cpp" "${SOURCE_CONTENT}") list(APPEND ALL_GENERATED_SOURCES "${GENERATED_DIR}/${FUNC_NAME}.cpp") # Build dispatcher data. # CLI name keeps original dashes. string(APPEND FORWARD_DECLARATIONS "int ${FUNC_NAME}_main(int argc, char* argv[]);\n") string(APPEND DISPATCH_ENTRIES " {\"${SOURCE_NAME}\", ${FUNC_NAME}_main},\n") list(APPEND SNIPPET_NAMES "${SOURCE_NAME}") endforeach() # Sort snippet names for the usage message. list(SORT SNIPPET_NAMES) set(SNIPPET_LIST_LITERAL "") foreach(_name ${SNIPPET_NAMES}) string(APPEND SNIPPET_LIST_LITERAL " \"${_name}\",\n") endforeach() # Generate the dispatcher from template, substituting @VARIABLES@. configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/snippet_dispatcher.cpp.in" "${GENERATED_DIR}/snippet_dispatcher.cpp" @ONLY ) # Build the single executable. add_executable(snippets "${GENERATED_DIR}/snippet_dispatcher.cpp" ${ALL_GENERATED_SOURCES} ) rerun_strict_warning_settings(snippets) # Renamed mains lose the compiler's special `main` treatment that suppresses # unused-parameter warnings for argc/argv. if(MSVC) target_compile_options(snippets PRIVATE /wd4100) else() target_compile_options(snippets PRIVATE -Wno-unused-parameter) endif() target_link_libraries(snippets PRIVATE rerun_sdk loguru::loguru rerun_arrow_target)