fprime/cmake/autocoder/autocoder.cmake
M Starch 74dd80e2b7
Add FPRIME_CMAKE_QUIET option (#4276)
* Add FPRIME_CMAKE_QUIET option

* Remove makefile deadcode

* Attempt CI debug

Add debugging message for FPRIME_SUB_BUILD_JOBS variable.

* Fix argument passing for sub-build jobs

* Update cmake/sub-build/sub-build.cmake

* Update cmake/sub-build/sub-build.cmake

---------

Co-authored-by: Thomas Boyer-Chammard <49786685+thomas-bc@users.noreply.github.com>
2025-10-08 16:30:36 -07:00

278 lines
14 KiB
CMake

####
# autocoder/autocoder.cmake:
#
# Autocoder setup and support file. This performs all of the general autocoder functions, running the specific functions
# defined within the individual autocoders. This gives the ability to run a set of autocoders to produce files.
#
# Note: autocoders need to be run by targets. See target/target.cmake.
####
include_guard()
include(utilities)
include(autocoder/helpers)
# Allowed return values
set(FPRIME_AUTOCODER_UNSUPPORTED AUTOCODER_GENERATED AUTOCODER_SCRIPT AUTOCODER_INPUTS AUTOCODER_INCLUDES)
set(FPRIME_AUTOCODER_REQUIRED AUTOCODER_GENERATED_AUTOCODER_INPUTS AUTOCODER_GENERATED_BUILD_SOURCES AUTOCODER_GENERATED_OTHER)
set(FPRIME_AUTOCODER_OPTIONAL AUTOCODER_DEPENDENCIES)
####
# run_ac_set:
#
# Run a set of autocoder allowing back-to-back execution of a set of autocoders. SOURCES are the source files that are
# input into the autocoder filters. Extra arguments may be an include path for an autocoder (e.g. autocoder/fpp).
#
# BUILD_TARGET_NAME: name of the build target getting attached autocoding
# SOURCES: source file input list
# ...: autocoder include
####
function (run_ac_set BUILD_TARGET_NAME)
# Get all sources available for autocode processing
get_target_property(AUTOCODER_INPUT_SOURCES "${BUILD_TARGET_NAME}" AUTOCODER_INPUTS)
# Get the source list, if passed in
set(AC_LIST)
if (ARGN)
set(AC_LIST "${ARGN}")
endif()
# Do not init GENERATED_FILE_LIST as it is read from previous AC runs above
# Create a hash of the autocoder set to isolate results
string(SHA1 "AC_SET_HASH" "${AC_LIST}")
foreach(AC_CMAKE IN LISTS AC_LIST)
run_ac("${BUILD_TARGET_NAME}" "${AC_CMAKE}" "${AUTOCODER_INPUT_SOURCES}" "${GENERATED_FILE_LIST}" "${AC_SET_HASH}")
get_property(AUTOCODER_GENERATED_AUTOCODER_INPUTS_VALUES TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AC_SET_HASH}_AUTOCODER_GENERATED_AUTOCODER_INPUTS")
list(APPEND AUTOCODER_INPUT_SOURCES ${AUTOCODER_GENERATED_AUTOCODER_INPUTS_VALUES})
endforeach()
# Read from hash-specific properties for this autocoder set
get_property(AUTOCODER_GENERATED_VALUES TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AC_SET_HASH}_AUTOCODER_GENERATED")
get_property(AUTOCODER_GENERATED_BUILD_SOURCES_VALUES TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AC_SET_HASH}_AUTOCODER_GENERATED_BUILD_SOURCES")
get_property(AUTOCODER_GENERATED_AUTOCODER_INPUTS_VALUES TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AC_SET_HASH}_AUTOCODER_GENERATED_AUTOCODER_INPUTS")
get_property(AUTOCODER_DEPENDENCIES_VALUES TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AC_SET_HASH}_AUTOCODER_DEPENDENCIES")
get_property(AUTOCODER_GENERATED_OTHER_VALUES TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AC_SET_HASH}_AUTOCODER_GENERATED_OTHER")
# Append to final target properties (aggregate of all runs)
append_list_property("${AUTOCODER_GENERATED_VALUES}" TARGET "${BUILD_TARGET_NAME}" PROPERTY AC_GENERATED)
append_list_property("${AUTOCODER_GENERATED_AUTOCODER_INPUTS_VALUES}" TARGET "${BUILD_TARGET_NAME}" PROPERTY AUTOCODER_INPUTS)
# Cannot use `target_sources` as it does not respect the "GENERATED" flag. Thus the sources need to be added
# to the SOURCES property directly.
append_list_property("${AUTOCODER_GENERATED_BUILD_SOURCES_VALUES}" TARGET "${BUILD_TARGET_NAME}" PROPERTY SOURCES)
append_list_property("${AUTOCODER_DEPENDENCIES_VALUES}" TARGET "${BUILD_TARGET_NAME}" PROPERTY LINK_LIBRARIES)
append_list_property("${AUTOCODER_DEPENDENCIES_VALUES}" TARGET "${BUILD_TARGET_NAME}" PROPERTY INTERFACE_LINK_LIBRARIES)
append_list_property("${AUTOCODER_DEPENDENCIES_VALUES}" TARGET "${BUILD_TARGET_NAME}" PROPERTY FPRIME_DEPENDENCIES)
# Invalidate the TRANSITIVE_DEPENDENCIES on the target
if (AUTOCODER_DEPENDENCIES_VALUES)
set_property(TARGET "${BUILD_TARGET_NAME}" PROPERTY TRANSITIVE_DEPENDENCIES)
endif()
# CMake claims that all generated files are marked generated. This asserts this fact.
get_target_property(ALL_GENERATED "${BUILD_TARGET_NAME}" AC_GENERATED)
foreach(SOURCE IN LISTS ALL_GENERATED)
get_source_file_property(IS_GENERATED ${SOURCE} GENERATED)
fprime_cmake_ASSERT("${SOURCE} is not marked generated." IS_GENERATED)
endforeach()
# Set variables in parent scope for this run's results
set(AUTOCODER_GENERATED "${AUTOCODER_GENERATED_VALUES}" PARENT_SCOPE)
set(AUTOCODER_GENERATED_BUILD_SOURCES "${AUTOCODER_GENERATED_BUILD_SOURCES_VALUES}" PARENT_SCOPE)
set(AUTOCODER_GENERATED_AUTOCODER_INPUTS "${AUTOCODER_GENERATED_AUTOCODER_INPUTS_VALUES}" PARENT_SCOPE)
set(AUTOCODER_DEPENDENCIES "${AUTOCODER_DEPENDENCIES_VALUES}" PARENT_SCOPE)
set(AUTOCODER_GENERATED_OTHER "${AUTOCODER_GENERATED_OTHER_VALUES}" PARENT_SCOPE)
endfunction()
####
# run_ac:
#
# Run the autocoder across the set of source files, SOURCES, and the previously generated sources, GENERATED_SOURCES.
# This will filter the SOURCES and GENERATED_SOURCES down to the handled set. Then for single-input autocoders, it runs
# the autocoder one input at a time, otherwise it runs the autocoder once on all inputs.
#
# AUTOCODER_CMAKE: cmake file containing autocoder definition
# SOURCES: sources input to run on the autocoder
####
function(run_ac BUILD_TARGET_NAME AUTOCODER_CMAKE SOURCES GENERATED_FILE_LIST HASH)
plugin_include_helper(AUTOCODER_NAME "${AUTOCODER_CMAKE}" is_supported setup_autocode get_generated_files get_dependencies)
# Normalize and filter source paths so that what we intend to run is in a standard form
normalize_paths(AC_INPUT_SOURCES "${SOURCES}")
_filter_sources(AC_INPUT_SOURCES "${AC_INPUT_SOURCES}")
# Break early if there are no sources, no need to autocode nothing
if (NOT AC_INPUT_SOURCES)
fprime_cmake_debug_message("[Autocode/${AUTOCODER_NAME}] No sources detected")
return()
endif()
# Check if this autocoder has been run before by comparing the hash of inputs. This allows us to skip running
# at a previous time. If so, skip autocoder and use old results
string(SHA1 "SRCS_HASH" "${AC_INPUT_SOURCES};${AUTOCODER_CMAKE}")
# Check if we have a previously stored hash and compare it
get_property(STORED_HASH TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AUTOCODER_NAME}_SRCS_HASH")
get_property(HASH_SET TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AUTOCODER_NAME}_SRCS_HASH" SET)
# If we have not run this autocoder before, or if the hash has changed, run the autocoder
if (NOT HASH_SET)
# Store the hash for future runs
set_property(TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AUTOCODER_NAME}_SRCS_HASH" "${SRCS_HASH}")
_describe_autocoder_prep("${AUTOCODER_NAME}" "${AC_INPUT_SOURCES}")
# Find the one variable set in the autocoder
get_property(HANDLES_INDIVIDUAL_SOURCES_SET GLOBAL PROPERTY "${AUTOCODER_NAME}_HANDLES_INDIVIDUAL_SOURCES" SET)
if (NOT HANDLES_INDIVIDUAL_SOURCES_SET)
message(FATAL_ERROR "${AUTOCODER_CMAKE} did not call one of the autocoder_setup_for_*_sources functions")
endif()
get_property(HANDLES_INDIVIDUAL_SOURCES GLOBAL PROPERTY "${AUTOCODER_NAME}_HANDLES_INDIVIDUAL_SOURCES")
# Handles individual/multiple source handling
if (HANDLES_INDIVIDUAL_SOURCES)
foreach(SOURCE IN LISTS AC_INPUT_SOURCES)
__ac_process_sources("${BUILD_TARGET_NAME}" "${SOURCE}")
endforeach()
else()
__ac_process_sources("${BUILD_TARGET_NAME}" "${AC_INPUT_SOURCES}")
endif()
else()
# Assert runs are identical for the same autocoder
fprime_cmake_ASSERT("Hash mismatch for autocoder ${AUTOCODER_NAME}: stored '${STORED_HASH}' vs calculated '${SRCS_HASH}'"
"${STORED_HASH}" STREQUAL "${SRCS_HASH}")
endif()
# Read autocoder outputs from properties using the centralized variable lists
set(ALL_AUTOCODER_VARIABLES ${FPRIME_AUTOCODER_REQUIRED} ${FPRIME_AUTOCODER_OPTIONAL} AUTOCODER_GENERATED)
# Process each autocoder variable and append to target properties with the same name
foreach(VARIABLE_NAME IN LISTS ALL_AUTOCODER_VARIABLES)
get_property(VARIABLE_VALUES TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AUTOCODER_NAME}_${VARIABLE_NAME}")
if (VARIABLE_VALUES)
append_list_property("${VARIABLE_VALUES}" TARGET "${BUILD_TARGET_NAME}" PROPERTY "${HASH}_${VARIABLE_NAME}")
endif()
endforeach()
_describe_autocoder_run("${AUTOCODER_NAME}")
endfunction(run_ac)
####
# Function `_describe_autocoder_prep`:
#
# Describes the inputs into an autocoder run. Does nothing unless CMAKE_DEBUG_OUTPUT is ON. Run before running the
# autocoder
#
# AUTOCODER_NAME: name of autocoder being run
# AC_INPUT_SOURCES: input files to autocoder
####
function(_describe_autocoder_prep AUTOCODER_NAME AC_INPUT_SOURCES)
# Start by displaying inputs to autocoders
fprime_cmake_debug_message("[Autocode/${AUTOCODER_NAME}] Autocoding Input Sources:")
foreach(SOURCE IN LISTS AC_INPUT_SOURCES)
fprime_cmake_debug_message("[Autocode/${AUTOCODER_NAME}] ${SOURCE}")
endforeach()
endfunction()
####
# Function `_describe_autocoder_run`:
#
# Describe the results of an autocoder run. Does nothing unless CMAKE_DEBUG_OUTPUT is ON. Must have run the autocoder
# already and set the properties.
#
# AUTOCODER_NAME: name of autocoder being described
####
function(_describe_autocoder_run AUTOCODER_NAME)
# When actually generating items, explain what is done and why
if (CMAKE_DEBUG_OUTPUT)
# Create a map of property names to display names
set(PROPERTY_DISPLAY_NAMES)
list(APPEND PROPERTY_DISPLAY_NAMES "AUTOCODER_GENERATED" "Generated Files")
list(APPEND PROPERTY_DISPLAY_NAMES "AUTOCODER_GENERATED_BUILD_SOURCES" "New Build Sources")
list(APPEND PROPERTY_DISPLAY_NAMES "AUTOCODER_GENERATED_AUTOCODER_INPUTS" "Additional Autocode Inputs")
list(APPEND PROPERTY_DISPLAY_NAMES "AUTOCODER_GENERATED_OTHER" "Other Generated Files")
list(APPEND PROPERTY_DISPLAY_NAMES "AUTOCODER_DEPENDENCIES" "Module Dependencies")
# Process all variables from the FPRIME_AUTOCODER lists plus AUTOCODER_GENERATED
set(ALL_AUTOCODER_VARIABLES ${FPRIME_AUTOCODER_REQUIRED} ${FPRIME_AUTOCODER_OPTIONAL} AUTOCODER_GENERATED)
foreach(VARIABLE_NAME IN LISTS ALL_AUTOCODER_VARIABLES)
# Find the display name for this variable
list(FIND PROPERTY_DISPLAY_NAMES "${VARIABLE_NAME}" NAME_INDEX)
if (NAME_INDEX GREATER_EQUAL 0)
math(EXPR DISPLAY_INDEX "${NAME_INDEX} + 1")
list(GET PROPERTY_DISPLAY_NAMES ${DISPLAY_INDEX} DISPLAY_NAME)
get_property(PROPERTY_VALUES TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AUTOCODER_NAME}_${VARIABLE_NAME}")
if (PROPERTY_VALUES)
fprime_cmake_debug_message("[Autocode/${AUTOCODER_NAME}] ${DISPLAY_NAME}:")
foreach(VALUE IN LISTS PROPERTY_VALUES)
fprime_cmake_debug_message("[Autocode/${AUTOCODER_NAME}] ${VALUE}")
endforeach()
endif()
endif()
endforeach()
endif()
endfunction()
####
# _filter_sources:
#
# Filters sources down to the ones supported by the active autocoder. It is an error to call this helper function before
# including an autocoder's CMake file and thus setting the active autocoder. Helper function.
#
# OUTPUT_NAME: name of output variable to set in parent scope
# ...: any number of arguments containing lists of sources
####
function(_filter_sources OUTPUT_NAME)
set(OUTPUT_LIST)
# Loop over the list and check
foreach (SOURCE_LIST IN LISTS ARGN)
foreach(SOURCE IN LISTS SOURCE_LIST)
cmake_language(CALL "${AUTOCODER_NAME}_is_supported" "${SOURCE}")
if (IS_SUPPORTED)
list(APPEND OUTPUT_LIST "${SOURCE}")
endif()
endforeach()
endforeach()
set(${OUTPUT_NAME} "${OUTPUT_LIST}" PARENT_SCOPE)
endfunction(_filter_sources)
####
# __ac_process_sources:
#
# Process sources found in SOURCES list and sets up the autocoder to run on the sources by registering a rule to create
# those sources.
# SOURCES: source file list. Note: if the autocoder sets HANDLES_INDIVIDUAL_SOURCES this will be singular
####
function(__ac_process_sources BUILD_TARGET_NAME SOURCES)
# Loop through the variables from the various lists and make sure they are undefined
foreach(VARIABLE IN LISTS FPRIME_AUTOCODER_UNSUPPORTED FPRIME_AUTOCODER_REQUIRED FPRIME_AUTOCODER_OPTIONAL)
fprime_cmake_ASSERT("'${VARIABLE}' set to '${${VARIABLE}}' before call" NOT DEFINED ${VARIABLE})
endforeach()
# Run the generation setup when not requesting "info only"
cmake_language(CALL "${AUTOCODER_NAME}_setup_autocode" "${BUILD_TARGET_NAME}" "${SOURCES}")
# Check for removed support
foreach(VARIABLE IN LISTS FPRIME_AUTOCODER_UNSUPPORTED)
fprime_cmake_ASSERT("Unsupported '${VARIABLE}' set to '${${VARIABLE}}' by autocoder ${AUTOCODER_NAME}"
NOT DEFINED ${VARIABLE})
endforeach()
# Search through requirements ensuring one was set
set(AUTOCODER_MET_REQUIRED FALSE)
foreach(REQUIREMENT IN LISTS FPRIME_AUTOCODER_REQUIRED)
if (DEFINED ${REQUIREMENT})
list(APPEND AUTOCODER_GENERATED ${${REQUIREMENT}})
set(AUTOCODER_MET_REQUIRED TRUE)
endif()
endforeach()
fprime_cmake_ASSERT("Autocoder must define at least one of: ${FPRIME_AUTOCODER_REQUIRED}" AUTOCODER_MET_REQUIRED)
# Set autocoder output variables as target properties
foreach(VARIABLE IN LISTS FPRIME_AUTOCODER_REQUIRED FPRIME_AUTOCODER_OPTIONAL)
if (DEFINED ${VARIABLE})
append_list_property("${${VARIABLE}}" TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AUTOCODER_NAME}_${VARIABLE}")
endif()
endforeach()
# Also set the calculated AUTOCODER_GENERATED
if (DEFINED AUTOCODER_GENERATED)
append_list_property("${AUTOCODER_GENERATED}" TARGET "${BUILD_TARGET_NAME}" PROPERTY "${AUTOCODER_NAME}_AUTOCODER_GENERATED")
endif()
endfunction()