mirror of
https://github.com/nasa/fprime.git
synced 2025-12-10 00:44:37 -06:00
* 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>
968 lines
40 KiB
CMake
968 lines
40 KiB
CMake
####
|
||
# utilities.cmake:
|
||
#
|
||
# Utility and support functions for the fprime CMake build system.
|
||
####
|
||
include_guard()
|
||
set_property(GLOBAL PROPERTY C_CPP_ASM_REGEX ".*\.(c|cpp|cc|cxx|S|asm)$")
|
||
|
||
####
|
||
# Function `sort_buildable_from_non_buildable_sources`:
|
||
#
|
||
# Sorts C/C++ "buildable" sources from other sources. This uses the GLOBAL property C_CPP_ASM_REGEX to
|
||
# determine how to sort. Ideally users would use SOURCES and AUTOCODER_INPUTS to distinguish but this
|
||
# provides some backwards compatibility with the merged SOURCE_FILES variable.
|
||
#
|
||
# - **BUILDABLE_SOURCE_OUTPUT**: output name for buildable sources to be set in parent scope
|
||
# - **NON_BUILDABLE_SOURCE_OUTPUT**: output name for non-buildable sources to be set in parent scope
|
||
####
|
||
function(sort_buildable_from_non_buildable_sources BUILDABLE_SOURCE_OUTPUT NON_BUILDABLE_SOURCE_OUTPUT)
|
||
get_property(SORT_REGEX GLOBAL PROPERTY C_CPP_ASM_REGEX )
|
||
set(CPP_LIST_NAME ${ARGN})
|
||
set(NON_CPP_LIST_NAME ${ARGN})
|
||
list(FILTER CPP_LIST_NAME INCLUDE REGEX "${SORT_REGEX}")
|
||
list(FILTER NON_CPP_LIST_NAME EXCLUDE REGEX "${SORT_REGEX}")
|
||
set("${BUILDABLE_SOURCE_OUTPUT}" ${CPP_LIST_NAME} PARENT_SCOPE)
|
||
set("${NON_BUILDABLE_SOURCE_OUTPUT}" ${NON_CPP_LIST_NAME} PARENT_SCOPE)
|
||
endfunction()
|
||
|
||
####
|
||
# Macro `clear_historical_variables`:
|
||
#
|
||
# Clears old variables `MOD_DEPS`, `SOURCE_FILES`, `HEADER_FILES`, etc. from the scope of the
|
||
# caller. This removes accidental uses of these variables within the refactored system from this
|
||
# scope and below.
|
||
#
|
||
# This is a macro to ensure the caller's scope is affected.
|
||
#
|
||
# **ARGN:** passed to the `unset` calls (for things like PARENT_SCOPE)
|
||
####
|
||
function(clear_historical_variables)
|
||
unset(SOURCE_FILES ${ARGN})
|
||
unset(MOD_DEPS ${ARGN})
|
||
unset(HEADER_FILES ${ARGN})
|
||
unset(UT_SOURCE_FILES ${ARGN})
|
||
unset(UT_MOD_DEPS ${ARGN})
|
||
unset(UT_HEADER_FILES ${ARGN})
|
||
endfunction()
|
||
|
||
####
|
||
# Function `plugin_name`:
|
||
#
|
||
# From a plugin include path retrieve the plugin name. This is the name without any .cmake extension.
|
||
#
|
||
# INCLUDE_PATH: path to plugin
|
||
# OUTPUT_VARIABLE: variable to set in caller's scope with result
|
||
####
|
||
function(plugin_name INCLUDE_PATH OUTPUT_VARIABLE)
|
||
get_filename_component(TEMP_NAME "${INCLUDE_PATH}" NAME_WE)
|
||
set("${OUTPUT_VARIABLE}" ${TEMP_NAME} PARENT_SCOPE)
|
||
endfunction(plugin_name)
|
||
|
||
####
|
||
# Function `generate_individual_function_call`:
|
||
#
|
||
# Generates a routing table entry for the faux cmake_language call for an individual function. This call consists of
|
||
# a single `elseif(name == function and ARGC == ARG_COUNT)` to support a call to the function with ARG_COUNT arguments.
|
||
# This is a helper function intended for use within `generate_faux_cmake_language`.
|
||
#
|
||
# OUTPUT_FILE: file to write these `elseif` blocks into
|
||
# FUNCTION: name of function to write out
|
||
# ARG_COUNT: number of args for this particular invocation of the call
|
||
####
|
||
function(generate_individual_function_call OUTPUT_FILE FUNCTION ARG_COUNT)
|
||
# Build an invocation string of the form: ${FUNCTION}("${ARGV2}" "${ARGV3}" ..."${ARG_COUNT -1 + 2}")
|
||
# Notice several properties:
|
||
# 1. Calling function by a substituted name
|
||
# 2. Arguments are specifically escaped. Thus **must** be done to ensure that empty, and list arguments are
|
||
# correctly handled. Otherwise they randomly expand or disappear
|
||
# 3. Arg numbers start at 2. This accounts for ARGV0==CALL and ARGV1==Function name in the calling function
|
||
set(ARG_STRING "")
|
||
math(EXPR BOUND "${ARG_COUNT} - 1")
|
||
foreach(ARG_IT RANGE "${BOUND}")
|
||
math(EXPR ARG_NUM "${ARG_IT} + 2")
|
||
set(ARG_STRING "${ARG_STRING} \"\${ARGV${ARG_NUM}}\"")
|
||
endforeach()
|
||
math(EXPR ARG_NUM "${ARG_COUNT} + 2")
|
||
set(INVOCATION "${FUNCTION}(${ARG_STRING})")
|
||
file(APPEND "${FAUX_FILE}" " elseif (\"\${FUNCTION_NAME}\" STREQUAL ${FUNCTION} AND \${ARGC} EQUAL ${ARG_NUM})\n")
|
||
file(APPEND "${FAUX_FILE}" " ${INVOCATION}\n")
|
||
endfunction(generate_individual_function_call)
|
||
|
||
####
|
||
# Function `generate_faux_cmake_language`:
|
||
#
|
||
# This function is used to setup a fake implementation of `cmake_language` calls on implementations of CMake that
|
||
# predate its creation. The facsimile is incomplete, but for the purposes of this build system, it will be sufficient
|
||
# meaning that it can route all the plugin functions correctly but specifically **not** arbitrary function calls.
|
||
#
|
||
# Functions supported by this call are expected in the GLOBAL property: CMAKE_LANGUAGE_ROUTE_LIST
|
||
#
|
||
# This is accomplished by writing out a CMake file that contains a macro that looks like the `cmake_language(CALL)`
|
||
# feature but is implemented by an `if (NAME == FUNCTION) FUNCTION() endif()` table. This file is built within and
|
||
# included when finished.
|
||
#
|
||
# In terms of performance:
|
||
# - Native `cmake_language(CALL)` is incredibly fast
|
||
# - This faux implementation is slow
|
||
# - Repetitive including of .cmake files to "switch" implementations (as done in fprime v3.0.0) is **much** slower
|
||
####
|
||
function(generate_faux_cmake_language)
|
||
set(FAUX_FILE "${CMAKE_BINARY_DIR}/cmake_language.cmake")
|
||
set(ARG_MAX 5)
|
||
file(WRITE "${FAUX_FILE}" "#### AUTOGENERATED, DO NOT EDIT ####\n")
|
||
file(APPEND "${FAUX_FILE}" "macro(cmake_language ACTION FUNCTION_NAME)\n")
|
||
file(APPEND "${FAUX_FILE}" " if (NOT \"\${ACTION}\" STREQUAL \"CALL\")\n")
|
||
file(APPEND "${FAUX_FILE}" " message(FATAL_ERROR \"Cannot use \${ACTION} with faux cmake_language\")\n")
|
||
file(APPEND "${FAUX_FILE}" " elseif (NOT COMMAND \"\${FUNCTION_NAME}\")\n")
|
||
file(APPEND "${FAUX_FILE}" " message(FATAL_ERROR \"Unknown function \${FUNCTION_NAME} supplied to faux cmake_language\")\n")
|
||
# Generate one if block set for each function in the routing database
|
||
get_property(FUNCTIONS GLOBAL PROPERTY CMAKE_LANGUAGE_ROUTE_LIST)
|
||
foreach(FUNCTION IN LISTS FUNCTIONS)
|
||
math(EXPR ARG_TOP "${ARG_MAX} + 2")
|
||
fprime_cmake_debug_message("Mimicking cmake_language(CALL ${FUNCTION} \"\${ARGV2}\" ... \"\${ARGV${ARG_TOP}}\"")
|
||
foreach(ARG_COUNT RANGE "${ARG_MAX}")
|
||
generate_individual_function_call("${FAUX_FILE}" "${FUNCTION}" ${ARG_COUNT})
|
||
endforeach()
|
||
file(APPEND "${FAUX_FILE}" " elseif (\"\${FUNCTION_NAME}\" STREQUAL ${FUNCTION})\n")
|
||
file(APPEND "${FAUX_FILE}" " message(FATAL_ERROR \"Faux cmake_language called with too-many arguments: \${ARGC}\")\n")
|
||
endforeach()
|
||
file(APPEND "${FAUX_FILE}" " endif()\n")
|
||
file(APPEND "${FAUX_FILE}" "endmacro(cmake_language)\n")
|
||
include("${FAUX_FILE}")
|
||
endfunction()
|
||
|
||
####
|
||
# Function `plugin_include_helper`:
|
||
#
|
||
# Designed to help include API files (targets, autocoders) in an efficient way within CMake. This function imports a
|
||
# CMake file and defines a `dispatch_<function>(PLUGIN_NAME ...)` function for each function name in ARGN. Thus users
|
||
# of the imported plugin can call `dispatch_<function>(PLUGIN_NAME ...)` to dispatch a function as implemented in a
|
||
# plugin.
|
||
#
|
||
# OUTPUT_VARIABLE: set with the plugin name that has last been included
|
||
# INCLUDE_PATH: path to file to include
|
||
####
|
||
function(plugin_include_helper OUTPUT_VARIABLE INCLUDE_PATH)
|
||
plugin_name("${INCLUDE_PATH}" PLUGIN_NAME)
|
||
# Get the global property of all function items
|
||
get_property(TEMP_LIST GLOBAL PROPERTY CMAKE_LANGUAGE_ROUTE_LIST)
|
||
set(CHANGED FALSE)
|
||
foreach(PLUGIN_FUNCTION IN LISTS ARGN)
|
||
# Include the file if we have not found the prefixed function name yet
|
||
if (NOT COMMAND "${PLUGIN_NAME}_${PLUGIN_FUNCTION}")
|
||
include("${INCLUDE_PATH}")
|
||
endif()
|
||
# Add the function if any of the set were determined missing
|
||
if (NOT "${PLUGIN_NAME}_${PLUGIN_FUNCTION}" IN_LIST TEMP_LIST)
|
||
set_property(GLOBAL APPEND PROPERTY CMAKE_LANGUAGE_ROUTE_LIST "${PLUGIN_NAME}_${PLUGIN_FUNCTION}")
|
||
set(CHANGED TRUE)
|
||
endif()
|
||
endforeach()
|
||
|
||
# If cmake_language is not available, we have to implement it
|
||
if(CHANGED AND ${CMAKE_VERSION} VERSION_LESS "3.18.0")
|
||
generate_faux_cmake_language()
|
||
endif()
|
||
set("${OUTPUT_VARIABLE}" "${PLUGIN_NAME}" PARENT_SCOPE)
|
||
endfunction(plugin_include_helper)
|
||
|
||
####
|
||
# starts_with:
|
||
#
|
||
# Check if the string input starts with the given prefix. Sets OUTPUT_VAR to TRUE when it does and sets OUTPUT_VAR to
|
||
# FALSE when it does not. OUTPUT_VAR is the name of the variable in PARENT_SCOPE that will be set.
|
||
#
|
||
# Note: regexs in CMake are known to be inefficient. Thus `starts_with` and `ends_with` are implemented without them
|
||
# in order to ensure speed.
|
||
#
|
||
# OUTPUT_VAR: variable to set
|
||
# STRING: string to check
|
||
# PREFIX: expected ending
|
||
####
|
||
function(starts_with OUTPUT_VAR STRING PREFIX)
|
||
set("${OUTPUT_VAR}" FALSE PARENT_SCOPE)
|
||
string(LENGTH "${PREFIX}" PREFIX_LENGTH)
|
||
string(SUBSTRING "${STRING}" "0" "${PREFIX_LENGTH}" FOUND_PREFIX)
|
||
# Check the substring
|
||
if (FOUND_PREFIX STREQUAL "${PREFIX}")
|
||
set("${OUTPUT_VAR}" TRUE PARENT_SCOPE)
|
||
endif()
|
||
endfunction(starts_with)
|
||
|
||
####
|
||
# ends_with:
|
||
#
|
||
# Check if the string input ends with the given suffix. Sets OUTPUT_VAR to TRUE when it does and sets OUTPUT_VAR to
|
||
# FALSE when it does not. OUTPUT_VAR is the name of the variable in PARENT_SCOPE that will be set.
|
||
#
|
||
# Note: regexs in CMake are known to be inefficient. Thus `starts_with` and `ends_with` are implemented without them
|
||
# in order to ensure speed.
|
||
#
|
||
# OUTPUT_VAR: variable to set
|
||
# STRING: string to check
|
||
# SUFFIX: expected ending
|
||
####
|
||
function(ends_with OUTPUT_VAR STRING SUFFIX)
|
||
set("${OUTPUT_VAR}" FALSE PARENT_SCOPE)
|
||
string(LENGTH "${STRING}" INPUT_LENGTH)
|
||
string(LENGTH "${SUFFIX}" SUFFIX_LENGTH)
|
||
if (INPUT_LENGTH GREATER_EQUAL SUFFIX_LENGTH)
|
||
# Calculate the substring of suffix length at end of string
|
||
math(EXPR START "${INPUT_LENGTH} - ${SUFFIX_LENGTH}")
|
||
string(SUBSTRING "${STRING}" "${START}" "${SUFFIX_LENGTH}" FOUND_SUFFIX)
|
||
# Check the substring
|
||
if (FOUND_SUFFIX STREQUAL "${SUFFIX}")
|
||
set("${OUTPUT_VAR}" TRUE PARENT_SCOPE)
|
||
endif()
|
||
endif()
|
||
endfunction(ends_with)
|
||
|
||
####
|
||
# init_variables:
|
||
#
|
||
# Initialize all variables passed in to empty variables in the calling scope.
|
||
####
|
||
function(init_variables)
|
||
foreach (VARIABLE IN LISTS ARGN)
|
||
set(${VARIABLE} "" PARENT_SCOPE)
|
||
endforeach()
|
||
endfunction(init_variables)
|
||
|
||
####
|
||
# normalize_paths:
|
||
#
|
||
# Take in any number of lists of paths and normalize the paths returning a single list.
|
||
# OUTPUT_NAME: name of variable to set in parent scope
|
||
####
|
||
function(normalize_paths OUTPUT_NAME)
|
||
set(OUTPUT_LIST)
|
||
# Loop over the list and check
|
||
foreach (PATH_LIST IN LISTS ARGN)
|
||
foreach(PATH IN LISTS PATH_LIST)
|
||
get_filename_component(PATH "${PATH}" ABSOLUTE)
|
||
list(APPEND OUTPUT_LIST "${PATH}")
|
||
endforeach()
|
||
endforeach()
|
||
set(${OUTPUT_NAME} "${OUTPUT_LIST}" PARENT_SCOPE)
|
||
endfunction(normalize_paths)
|
||
|
||
####
|
||
# resolve_dependencies:
|
||
#
|
||
# Sets OUTPUT_VAR in parent scope to be the set of dependencies in canonical form: relative path from root replacing
|
||
# directory separators with "_". E.g. fprime/Fw/Time becomes Fw_Time.
|
||
#
|
||
# OUTPUT_VAR: variable to fill in parent scope
|
||
# ARGN: list of dependencies to resolve
|
||
####
|
||
function(resolve_dependencies OUTPUT_VAR)
|
||
# Resolve all dependencies
|
||
set(RESOLVED)
|
||
foreach(DEPENDENCY IN LISTS ARGN)
|
||
# No resolution is done on linker-only dependencies
|
||
linker_only(LINKER_ONLY "${DEPENDENCY}")
|
||
if (LINKER_ONLY)
|
||
list(APPEND RESOLVED "${DEPENDENCY}")
|
||
continue()
|
||
endif()
|
||
get_module_name(${DEPENDENCY})
|
||
if (NOT MODULE_NAME IN_LIST RESOLVED)
|
||
list(APPEND RESOLVED "${MODULE_NAME}")
|
||
endif()
|
||
endforeach()
|
||
set(${OUTPUT_VAR} "${RESOLVED}" PARENT_SCOPE)
|
||
endfunction(resolve_dependencies)
|
||
|
||
####
|
||
# Function `is_target_real`:
|
||
#
|
||
# Does this target represent a real item (executable, library)? OUTPUT is set to TRUE when real, and FALSE otherwise.
|
||
# Non-real targets include TARGET_TYPE=UTILITY and ALIASED_TARGET.
|
||
#
|
||
# OUTPUT: variable to set
|
||
# TEST_TARGET: target to set
|
||
####
|
||
function(is_target_real OUTPUT TEST_TARGET)
|
||
if (TARGET "${DEPENDENCY}")
|
||
get_target_property(TARGET_TYPE "${DEPENDENCY}" TYPE)
|
||
# Make sure this is not a utility target
|
||
get_target_property(IS_ALIAS "${TEST_TARGET}" ALIASED_TARGET)
|
||
if (NOT TARGET_TYPE STREQUAL "UTILITY" AND NOT IS_ALIAS)
|
||
set("${OUTPUT}" TRUE PARENT_SCOPE)
|
||
return()
|
||
endif()
|
||
endif()
|
||
set("${OUTPUT}" FALSE PARENT_SCOPE)
|
||
endfunction()
|
||
|
||
####
|
||
# Function `is_target_library`:
|
||
#
|
||
# Does this target represent a real library? OUTPUT is set to TRUE when real, and FALSE otherwise.
|
||
#
|
||
# OUTPUT: variable to set
|
||
# TEST_TARGET: target to set
|
||
####
|
||
function(is_target_library OUTPUT TEST_TARGET)
|
||
set("${OUTPUT}" FALSE PARENT_SCOPE)
|
||
if (TARGET "${TEST_TARGET}")
|
||
get_target_property(TARGET_TYPE "${TEST_TARGET}" TYPE)
|
||
ends_with(IS_LIBRARY "${TARGET_TYPE}" "_LIBRARY")
|
||
set("${OUTPUT}" "${IS_LIBRARY}" PARENT_SCOPE)
|
||
endif()
|
||
endfunction()
|
||
|
||
####
|
||
# linker_only:
|
||
#
|
||
# Checks if a given dependency should be supplied to the linker only. These will not be supplied as CMake dependencies
|
||
# but will be supplied as link libraries. These tokens are of several types:
|
||
#
|
||
# 1. Linker flags: starts with -l
|
||
# 2. Existing Files: accounts for preexisting libraries shared and otherwise
|
||
#
|
||
# OUTPUT_VAR: variable to set in PARENT_SCOPE to TRUE/FALSE
|
||
# TOKEN: token to check if "linker only"
|
||
####
|
||
function(linker_only OUTPUT_VAR TOKEN)
|
||
set("${OUTPUT_VAR}" FALSE PARENT_SCOPE)
|
||
starts_with(IS_LINKER_FLAG "${TOKEN}" "-l")
|
||
if (IS_LINKER_FLAG OR (EXISTS "${TOKEN}" AND NOT IS_DIRECTORY "${TOKEN}"))
|
||
set("${OUTPUT_VAR}" TRUE PARENT_SCOPE)
|
||
endif()
|
||
endfunction()
|
||
|
||
####
|
||
# build_relative_path:
|
||
#
|
||
# Calculate the path to an item relative to known build paths. Search is performed in the following order erring if the
|
||
# item is found in multiple paths.
|
||
#
|
||
# INPUT_PATH: input path to search
|
||
# OUTPUT_VAR: output variable to fill
|
||
####
|
||
function(build_relative_path INPUT_PATH OUTPUT_VAR)
|
||
# Implementation assertion
|
||
if (NOT DEFINED FPRIME_BUILD_LOCATIONS)
|
||
message(FATAL_ERROR "FPRIME_BUILD_LOCATIONS not set before build_relative_path was called")
|
||
endif()
|
||
normalize_paths(FPRIME_LOCS_NORM ${FPRIME_BUILD_LOCATIONS})
|
||
normalize_paths(INPUT_PATH ${INPUT_PATH})
|
||
foreach(PARENT IN LISTS FPRIME_LOCS_NORM)
|
||
string(REGEX REPLACE "${PARENT}/(.*)$" "\\1" LOC_TEMP "${INPUT_PATH}")
|
||
if (NOT LOC_TEMP STREQUAL INPUT_PATH AND NOT LOC_TEMP MATCHES "${LOC}$")
|
||
message(FATAL_ERROR "Found ${INPUT_PATH} at multiple locations: ${LOC} and ${LOC_TEMP}")
|
||
elseif(NOT LOC_TEMP STREQUAL INPUT_PATH AND NOT DEFINED LOC)
|
||
set(LOC "${LOC_TEMP}")
|
||
endif()
|
||
endforeach()
|
||
if (LOC STREQUAL "")
|
||
message(FATAL_ERROR "Failed to find location for: ${INPUT_PATH}")
|
||
endif()
|
||
set(${OUTPUT_VAR} ${LOC} PARENT_SCOPE)
|
||
endfunction(build_relative_path)
|
||
|
||
####
|
||
# on_any_changed:
|
||
#
|
||
# Sets VARIABLE to true if any file has been noted as changed from the "on_changed" function. Will create cache files
|
||
# in the binary directory. Please see: on_changed
|
||
#
|
||
# INPUT_FILES: files to check for changes
|
||
# ARGN: passed into execute_process via on_changed call
|
||
####
|
||
function (on_any_changed INPUT_FILES VARIABLE)
|
||
foreach(INPUT_FILE IN LISTS INPUT_FILES)
|
||
on_changed("${INPUT_FILE}" TEMP_ON_CHANGED ${ARGN})
|
||
if (TEMP_ON_CHANGED)
|
||
set(${VARIABLE} TRUE PARENT_SCOPE)
|
||
return()
|
||
endif()
|
||
endforeach()
|
||
set(${VARIABLE} FALSE PARENT_SCOPE)
|
||
endfunction()
|
||
|
||
####
|
||
# on_changed:
|
||
#
|
||
# Sets VARIABLE to true if and only if the given file has changed since the last time this function was invoked. It will
|
||
# create "${INPUT_FILE}.prev" in the binary directory as a cache from the previous invocation. The result is always TRUE
|
||
# unless a successful no-difference is calculated.
|
||
#
|
||
# INPUT_FILE: file to check if it has changed
|
||
# ARGN: passed into execute_process
|
||
####
|
||
function (on_changed INPUT_FILE VARIABLE)
|
||
get_filename_component(INPUT_BASENAME "${INPUT_FILE}" NAME)
|
||
set(PREVIOUS_FILE "${CMAKE_CURRENT_BINARY_DIR}/${INPUT_BASENAME}.prev")
|
||
|
||
execute_process(COMMAND "${CMAKE_COMMAND}" -E compare_files "${INPUT_FILE}" "${PREVIOUS_FILE}"
|
||
RESULT_VARIABLE difference OUTPUT_QUIET ERROR_QUIET)
|
||
# Files are the same, leave this function
|
||
if (difference EQUAL 0)
|
||
set(${VARIABLE} FALSE PARENT_SCOPE)
|
||
return()
|
||
endif()
|
||
set(${VARIABLE} TRUE PARENT_SCOPE)
|
||
# Update the file with the latest
|
||
if (EXISTS "${INPUT_FILE}")
|
||
execute_process(COMMAND "${CMAKE_COMMAND}" -E copy "${INPUT_FILE}" "${PREVIOUS_FILE}" OUTPUT_QUIET)
|
||
endif()
|
||
endfunction()
|
||
|
||
####
|
||
# read_from_lines:
|
||
#
|
||
# Reads a set of variables from a newline delimited test base. This will read each variable as a separate line. It is
|
||
# based on the number of arguments passed in.
|
||
####
|
||
function (read_from_lines CONTENT)
|
||
# Loop through each arg
|
||
foreach(NAME IN LISTS ARGN)
|
||
string(REGEX MATCH "^([^\r\n]+)" VALUE "${CONTENT}")
|
||
string(REGEX REPLACE "^([^\r\n]*)\r?\n(.*)" "\\2" CONTENT "${CONTENT}")
|
||
set(${NAME} "${VALUE}" PARENT_SCOPE)
|
||
endforeach()
|
||
endfunction()
|
||
|
||
####
|
||
# Function `full_path_from_build_relative_path`:
|
||
#
|
||
# Creates a full path from the shortened build-relative path.
|
||
# -**SHORT_PATH:** build relative path
|
||
# Return: full path from relative path
|
||
####
|
||
function(full_path_from_build_relative_path SHORT_PATH OUTPUT_VARIABLE)
|
||
foreach(FPRIME_LOCATION IN LISTS FPRIME_BUILD_LOCATIONS)
|
||
if (EXISTS "${FPRIME_LOCATION}/${SHORT_PATH}")
|
||
set("${OUTPUT_VARIABLE}" "${FPRIME_LOCATION}/${SHORT_PATH}" PARENT_SCOPE)
|
||
return()
|
||
endif()
|
||
endforeach()
|
||
set("${OUTPUT_VARIABLE}" "" PARENT_SCOPE)
|
||
endfunction(full_path_from_build_relative_path)
|
||
|
||
####
|
||
# Function `get_nearest_build_root`:
|
||
#
|
||
# Finds the nearest build root from ${FPRIME_BUILD_LOCATIONS} that is a parent of DIRECTORY_PATH.
|
||
#
|
||
# - **DIRECTORY_PATH:** path to detect nearest build root
|
||
# Return: nearest parent from ${FPRIME_BUILD_LOCATIONS}
|
||
####
|
||
function(get_nearest_build_root DIRECTORY_PATH)
|
||
get_filename_component(DIRECTORY_PATH "${DIRECTORY_PATH}" ABSOLUTE)
|
||
set(FOUND_BUILD_ROOT "${DIRECTORY_PATH}")
|
||
set(LAST_REL "${DIRECTORY_PATH}")
|
||
foreach(FPRIME_BUILD_LOC ${FPRIME_BUILD_LOCATIONS} ${CMAKE_BINARY_DIR}/F-Prime ${CMAKE_BINARY_DIR})
|
||
get_filename_component(FPRIME_BUILD_LOC "${FPRIME_BUILD_LOC}" ABSOLUTE)
|
||
file(RELATIVE_PATH TEMP_MODULE ${FPRIME_BUILD_LOC} ${DIRECTORY_PATH})
|
||
string(LENGTH "${LAST_REL}" LEN1)
|
||
string(LENGTH "${TEMP_MODULE}" LEN2)
|
||
if (LEN2 LESS LEN1 AND TEMP_MODULE MATCHES "^[^./].*")
|
||
set(FOUND_BUILD_ROOT "${FPRIME_BUILD_LOC}")
|
||
set(LAST_REL "${TEMP_MODULE}")
|
||
endif()
|
||
endforeach()
|
||
if ("${FOUND_BUILD_ROOT}" STREQUAL "${DIRECTORY_PATH}")
|
||
message(FATAL_ERROR "No build root found for: ${DIRECTORY_PATH}")
|
||
endif()
|
||
set(FPRIME_CLOSEST_BUILD_ROOT "${FOUND_BUILD_ROOT}" PARENT_SCOPE)
|
||
endfunction()
|
||
####
|
||
# Function `get_module_name`:
|
||
#
|
||
# Takes a path, or something path-like and returns the module's name. This breaks down as the
|
||
# following:
|
||
#
|
||
# 1. If passed a path, the module name is the '_'ed variant of the relative path from BUILD_ROOT
|
||
# 2. If passes something which does not exist on the file system, it is just '_'ed
|
||
#
|
||
# i.e. ${BUILD_ROOT}/Svc/EventManager becomes Svc_EventManager
|
||
# Svc/EventManager also becomes Svc_EventManager
|
||
#
|
||
# - **DIRECTORY_PATH:** (optional) path to infer MODULE_NAME from. Default: CMAKE_CURRENT_LIST_DIR
|
||
# - **Return: MODULE_NAME** (set in parent scope)
|
||
####
|
||
function(get_module_name)
|
||
# Set optional arguments
|
||
if (ARGN)
|
||
set(DIRECTORY_PATH "${ARGN}")
|
||
else()
|
||
set(DIRECTORY_PATH "${CMAKE_CURRENT_LIST_DIR}")
|
||
endif()
|
||
resolve_path_variables(DIRECTORY_PATH)
|
||
# If DIRECTORY_PATH exists, then find its offset from BUILD_ROOT to calculate the module
|
||
# name. If it does not exist, then it is assumed to be an offset already and is carried
|
||
# forward in the calculation.
|
||
if (EXISTS ${DIRECTORY_PATH} AND IS_ABSOLUTE ${DIRECTORY_PATH})
|
||
# Module names a based on the current directory, not a file
|
||
if (NOT IS_DIRECTORY ${DIRECTORY_PATH})
|
||
get_filename_component(DIRECTORY_PATH "${DIRECTORY_PATH}" DIRECTORY)
|
||
endif()
|
||
# Get path name relative to the root directory
|
||
get_nearest_build_root(${DIRECTORY_PATH})
|
||
File(RELATIVE_PATH TEMP_MODULE_NAME ${FPRIME_CLOSEST_BUILD_ROOT} ${DIRECTORY_PATH})
|
||
else()
|
||
set(TEMP_MODULE_NAME ${DIRECTORY_PATH})
|
||
endif()
|
||
# Replace slash with underscore to have valid name
|
||
string(REPLACE "/" "_" TEMP_MODULE_NAME ${TEMP_MODULE_NAME})
|
||
set(MODULE_NAME ${TEMP_MODULE_NAME} PARENT_SCOPE)
|
||
endfunction(get_module_name)
|
||
|
||
####
|
||
# Function `get_expected_tool_version`:
|
||
#
|
||
# Gets the expected tool version named using version identifier VID to name the tools package
|
||
# file. This will be returned via the variable supplied in FILL_VARIABLE setting it in PARENT_SCOPE.
|
||
####
|
||
function(get_expected_tool_version VID FILL_VARIABLE)
|
||
find_program(TOOLS_CHECK NAMES fprime-version-check REQUIRED)
|
||
|
||
# Try project root as a source
|
||
set(REQUIREMENT_FILE "${FPRIME_PROJECT_ROOT}/requirements.txt")
|
||
if (EXISTS "${REQUIREMENT_FILE}")
|
||
execute_process(COMMAND "${TOOLS_CHECK}" "${VID}" "${REQUIREMENT_FILE}" OUTPUT_VARIABLE VERSION_TEXT ERROR_VARIABLE ERRORS RESULT_VARIABLE RESULT_OUT OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||
fprime_cmake_debug_message("[VERSION] Could not detect version from: ${REQUIREMENT_FILE}. ${ERRORS}")
|
||
if (RESULT_OUT EQUAL 0)
|
||
set("${FILL_VARIABLE}" "${VERSION_TEXT}" PARENT_SCOPE)
|
||
return()
|
||
endif()
|
||
endif()
|
||
# Fallback to requirements.txt in fprime
|
||
set(REQUIREMENT_FILE "${FPRIME_FRAMEWORK_PATH}/requirements.txt")
|
||
execute_process(COMMAND "${TOOLS_CHECK}" "${VID}" "${REQUIREMENT_FILE}" OUTPUT_VARIABLE VERSION_TEXT ERROR_VARIABLE ERRORS RESULT_VARIABLE RESULT_OUT OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||
if (RESULT_OUT EQUAL 0)
|
||
set("${FILL_VARIABLE}" "${VERSION_TEXT}" PARENT_SCOPE)
|
||
return()
|
||
endif()
|
||
fprime_cmake_warning("[VERSION] Could not detect version from: ${REQUIREMENT_FILE}. ${ERRORS}. Skipping check.")
|
||
set("${FILL_VARIABLE}" "" PARENT_SCOPE)
|
||
endfunction(get_expected_tool_version)
|
||
|
||
####
|
||
# Function `set_assert_flags`:
|
||
#
|
||
# Adds a -DASSERT_FILE_ID=(First 8 digits of MD5) to each source file, and records the output in
|
||
# hashes.txt. This allows for asserts on file ID not string. Also adds the -DASSERT_RELATIVE_PATH
|
||
# flag for handling relative path asserts.
|
||
####
|
||
function(set_assert_flags SRC)
|
||
if (NOT SRC MATCHES "^[$].*") # skip if generator expression
|
||
get_nearest_build_root("${SRC}") # sets FPRIME_CLOSEST_BUILD_ROOT in current scope
|
||
endif()
|
||
get_filename_component(FPRIME_CLOSEST_BUILD_ROOT_ABS "${FPRIME_CLOSEST_BUILD_ROOT}" ABSOLUTE)
|
||
get_filename_component(FPRIME_PROJECT_ROOT_ABS "${FPRIME_PROJECT_ROOT}" ABSOLUTE)
|
||
string(REPLACE "${FPRIME_CLOSEST_BUILD_ROOT_ABS}/" "" SHORT_SRC "${SRC}")
|
||
string(REPLACE "${FPRIME_PROJECT_ROOT_ABS}/" "" SHORT_SRC "${SHORT_SRC}")
|
||
|
||
string(MD5 HASH_VAL "${SHORT_SRC}")
|
||
string(SUBSTRING "${HASH_VAL}" 0 8 HASH_32)
|
||
file(APPEND "${CMAKE_BINARY_DIR}/hashes.txt" "${SHORT_SRC}: 0x${HASH_32}\n")
|
||
SET_SOURCE_FILES_PROPERTIES(${SRC} PROPERTIES COMPILE_FLAGS "-DASSERT_FILE_ID=0x${HASH_32} -DASSERT_RELATIVE_PATH='\"${SHORT_SRC}\"'")
|
||
endfunction(set_assert_flags)
|
||
|
||
|
||
####
|
||
# Function `print_property`:
|
||
#
|
||
# Prints a given property for the module.
|
||
# - **TARGET**: target to print properties
|
||
# - **PROPERTY**: name of property to print
|
||
####
|
||
function (print_property TARGET PROPERTY)
|
||
get_target_property(OUT "${TARGET}" "${PROPERTY}")
|
||
if (NOT OUT MATCHES ".*-NOTFOUND")
|
||
fprime_cmake_status("[F´ Module] ${TARGET} ${PROPERTY}:")
|
||
foreach (PROPERTY IN LISTS OUT)
|
||
fprime_cmake_status("[F´ Module] ${PROPERTY}")
|
||
endforeach()
|
||
endif()
|
||
endfunction(print_property)
|
||
|
||
####
|
||
# Function `introspect`:
|
||
#
|
||
# Prints the dependency list of the module supplied as well as the include directories.
|
||
#
|
||
# - **MODULE_NAME**: module name to print dependencies
|
||
####
|
||
function(introspect MODULE_NAME)
|
||
print_property("${MODULE_NAME}" SOURCES)
|
||
print_property("${MODULE_NAME}" SUPPLIED_HEADERS)
|
||
print_property("${MODULE_NAME}" INCLUDE_DIRECTORIES)
|
||
print_property("${MODULE_NAME}" LINK_LIBRARIES)
|
||
print_property("${MODULE_NAME}" INTERFACE_LINK_LIBRARIES)
|
||
endfunction(introspect)
|
||
|
||
####
|
||
# Function `execute_process_or_fail`:
|
||
#
|
||
# Calls CMake's `execute_process` with the arguments passed in via ARGN. This call is wrapped to print out the command
|
||
# line invocation when CMAKE_DEBUG_OUTPUT is set ON, and will check that the command processes correctly. Any error
|
||
# message is output should the command fail. No handling is done of standard error.
|
||
#
|
||
# Errors are determined by checking the process's return code where a FATAL_ERROR is produced on non-zero.
|
||
#
|
||
# - **ERROR_MESSAGE**: message to output should an error occurs
|
||
####
|
||
function(execute_process_or_fail ERROR_MESSAGE)
|
||
# Quiet standard output unless we are doing verbose output (handled below)
|
||
set(OUTPUT_ARGS OUTPUT_QUIET)
|
||
# Print the invocation if debug output is set
|
||
set(OUTPUT_ARGS)
|
||
set(COMMAND_AS_STRING "")
|
||
foreach(ARG IN LISTS ARGN)
|
||
set(COMMAND_AS_STRING "${COMMAND_AS_STRING}\"${ARG}\" ")
|
||
endforeach()
|
||
fprime_cmake_debug_message("[cli] ${COMMAND_AS_STRING}")
|
||
# Ninja pipes stderr to stdout so remove quiet output to see errors
|
||
if (CMAKE_GENERATOR MATCHES "Ninja")
|
||
set(OUTPUT_ARGS)
|
||
endif()
|
||
execute_process(
|
||
COMMAND ${ARGN}
|
||
RESULT_VARIABLE RETURN_CODE
|
||
OUTPUT_VARIABLE STANDARD_OUTPUT
|
||
ERROR_VARIABLE STANDARD_ERROR
|
||
ERROR_STRIP_TRAILING_WHITESPACE
|
||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||
${OUTPUT_ARGS}
|
||
)
|
||
if (NOT RETURN_CODE EQUAL 0)
|
||
set(FATAL_MESSAGE "${ERROR_MESSAGE}:\n${STANDARD_ERROR}")
|
||
# Ninja pipes stderr to stdout so we have to print stdout to see errors
|
||
if (CMAKE_GENERATOR MATCHES "Ninja")
|
||
string(APPEND FATAL_MESSAGE "\n${STANDARD_OUTPUT}")
|
||
endif()
|
||
message(FATAL_ERROR "${FATAL_MESSAGE}")
|
||
endif()
|
||
endfunction()
|
||
|
||
####
|
||
# Function `append_list_property`:
|
||
#
|
||
# Appends the NEW_ITEM to a property. ARGN is a set of arguments that are passed into the get and set property calls.
|
||
# This function calls get_property with ARGN appends NEW_ITEM to the result and then turns around and calls set_property
|
||
# with the new list. Callers **should not** supply the variable name argument to get_property.
|
||
#
|
||
# Duplicate entries are removed.
|
||
#
|
||
# Args:
|
||
# - `NEW_ITEM`: item to append to the property
|
||
# - `ARGN`: list of arguments forwarded to get and set property calls.
|
||
####
|
||
function(append_list_property NEW_ITEM)
|
||
get_property(LOCAL_COPY ${ARGN})
|
||
list(APPEND LOCAL_COPY ${NEW_ITEM})
|
||
list(REMOVE_DUPLICATES LOCAL_COPY)
|
||
set_property(${ARGN} "${LOCAL_COPY}")
|
||
endfunction()
|
||
|
||
####
|
||
# Function `filter_lists`:
|
||
#
|
||
# Filters lists set in ARGN to to ensure that they are not in the exclude list. Sets the <LIST>_FILTERED variable in
|
||
# PARENT_SCOPE with the results
|
||
# **EXCLUDE_LIST**: list of items to filter-out of ARGN lists
|
||
# **ARGN:** list of list names in parent scope to filter
|
||
####
|
||
function (filter_lists EXCLUDE_LIST)
|
||
foreach(SOURCE_LIST IN LISTS ARGN)
|
||
set(${SOURCE_LIST}_FILTERED "")
|
||
foreach(SOURCE IN LISTS ${SOURCE_LIST})
|
||
if (NOT SOURCE IN_LIST EXCLUDE_LIST)
|
||
list(APPEND ${SOURCE_LIST}_FILTERED "${SOURCE}")
|
||
endif()
|
||
endforeach()
|
||
set(${SOURCE_LIST}_FILTERED "${${SOURCE_LIST}_FILTERED}" PARENT_SCOPE)
|
||
endforeach()
|
||
endfunction(filter_lists)
|
||
|
||
####
|
||
# Function `get_fprime_library_option_string`:
|
||
#
|
||
# Returns a standard library option string from a name. Library option strings are derived from the directory and
|
||
# converted to a set of valid characters: [A-Z0-9_]. Alphabetic characters are made uppercase, numeric characters are
|
||
# maintained, and other characters are replaced with _.
|
||
#
|
||
# If multiple directories convert to the same name, these are effectively merged with respect to library options.
|
||
#
|
||
# OUTPUT_VAR: output variable to be set in parent scope
|
||
# LIBRARY_NAME: library name to convert to option
|
||
####
|
||
function(get_fprime_library_option_string OUTPUT_VAR LIBRARY_NAME)
|
||
string(TOUPPER "${LIBRARY_NAME}" LIBRARY_NAME_UPPER)
|
||
string(REGEX REPLACE "[^A-Z0-9_]" "_" LIBRARY_OPTION "${LIBRARY_NAME_UPPER}")
|
||
set("${OUTPUT_VAR}" "${LIBRARY_OPTION}" PARENT_SCOPE)
|
||
endfunction(get_fprime_library_option_string)
|
||
|
||
####
|
||
# Function `resolve_path_variables`:
|
||
#
|
||
# Resolve paths updating parent scope. ARGN should contain a list of variables to update.
|
||
#
|
||
# ARGN: list of variables to update
|
||
####
|
||
function(resolve_path_variables)
|
||
# Loop through all variables
|
||
foreach (INPUT_NAME IN LISTS ARGN)
|
||
set(NEW_LIST)
|
||
# Loop through each item in INPUT_NAME
|
||
foreach(UNRESOLVED IN LISTS ${INPUT_NAME})
|
||
get_filename_component(ABSOLUTE_UNRESOLVED "${UNRESOLVED}" ABSOLUTE)
|
||
# If it is a path, resolve it
|
||
if (EXISTS ${ABSOLUTE_UNRESOLVED})
|
||
get_filename_component(RESOLVED "${ABSOLUTE_UNRESOLVED}" REALPATH)
|
||
else()
|
||
set(RESOLVED "${UNRESOLVED}")
|
||
endif()
|
||
list(APPEND NEW_LIST "${RESOLVED}")
|
||
endforeach()
|
||
set("${INPUT_NAME}" "${NEW_LIST}" PARENT_SCOPE)
|
||
endforeach()
|
||
endfunction(resolve_path_variables)
|
||
|
||
####
|
||
# Function `fprime_cmake_fatal_error`:
|
||
#
|
||
# Prints a fatal error message to the user, highlighted with ---- to make it obvious. For multi-line
|
||
# messages, place a \n at the end of the previous message.
|
||
#
|
||
# - **ARGN**: message(s) to print separated by ' 's
|
||
####
|
||
function(fprime_cmake_fatal_error)
|
||
fprime_cmake_clear_message(FATAL_ERROR ${ARGN})
|
||
endfunction(fprime_cmake_fatal_error)
|
||
|
||
####
|
||
# Function `fprime_cmake_warning`:
|
||
#
|
||
# Prints a warning message to the user, highlighted with ---- to make it obvious. For multi-line
|
||
# messages, place a \n at the end of the previous message.
|
||
#
|
||
# - **ARGN**: message(s) to print separated by ' 's
|
||
####
|
||
function(fprime_cmake_warning)
|
||
fprime_cmake_clear_message(WARNING ${ARGN})
|
||
endfunction(fprime_cmake_warning)
|
||
|
||
####
|
||
# Function `fprime_cmake_status`:
|
||
#
|
||
# Prints a status message to the user that can be quieted with FPRIME_CMAKE_QUIET=ON.
|
||
# - **ARGN**: arguments to CMake's message() function w/o severity level
|
||
####
|
||
function(fprime_cmake_status)
|
||
if (NOT FPRIME_CMAKE_QUIET)
|
||
message(STATUS ${ARGN})
|
||
endif()
|
||
endfunction(fprime_cmake_status)
|
||
|
||
####
|
||
# Function `fprime_cmake_debug_message`:
|
||
#
|
||
# Prints a debug message.
|
||
#
|
||
# - **MESSAGE**: message to print
|
||
####
|
||
function(fprime_cmake_debug_message MESSAGE)
|
||
if (CMAKE_DEBUG_OUTPUT)
|
||
message(STATUS " [DEBUG] ${MESSAGE}")
|
||
endif()
|
||
endfunction(fprime_cmake_debug_message)
|
||
|
||
####
|
||
# Function `fprime__cmake_clear_message`:
|
||
#
|
||
# Prints a message to the user, highlighted with ---- to make it obvious and including the list file
|
||
# that is failing. For multi-line messages, place a \n at the end of the previous message.
|
||
#
|
||
# - **SEVERITY**: message severity to use
|
||
# - **ARGN**: message(s) to print separated by ' 's
|
||
####
|
||
function(fprime_cmake_clear_message SEVERITY)
|
||
string(REPLACE ";" " " MESSAGE "${ARGN}")
|
||
message("${SEVERITY}" " ----------------------------------------\n"
|
||
" ${MESSAGE} in:\n"
|
||
" ${CMAKE_CURRENT_LIST_FILE}\n"
|
||
" ----------------------------------------\n")
|
||
endfunction()
|
||
|
||
####
|
||
# Macro `fprime_cmake_ASSERT`:
|
||
#
|
||
# Checks condition, prints message. This is a macro so the condition is pasted into the message as well as
|
||
# the conditional clause.
|
||
#
|
||
# - **CONDITION**: condition to evaluate with if (${CONDITION})
|
||
####
|
||
macro(fprime_cmake_ASSERT MESSAGE)
|
||
# Simplify the evaluation of the condition by not placing NOT in front. Just have a no-op if clause
|
||
# where the else prints the FATAL message.
|
||
if (${ARGN})
|
||
else ()
|
||
string(REPLACE ";" " " FPRIME_INTERNAL_STRING_FROM_ARGN "${ARGN}")
|
||
message(FATAL_ERROR " ----------------------------------------\n"
|
||
" Assertion (${FPRIME_INTERNAL_STRING_FROM_ARGN}) failed with message '${MESSAGE}'. In:\n"
|
||
" ${CMAKE_CURRENT_FUNCTION_LIST_FILE}:${CMAKE_CURRENT_FUNCTION_LIST_LINE}\n"
|
||
" ----------------------------------------\n")
|
||
endif()
|
||
endmacro()
|
||
|
||
####
|
||
# Function `recurse_target_properties`:
|
||
#
|
||
# Recurses the supplied PROPERTY_NAMES of the CMAKE_BUILD_TARGET_NAME target. Sets three variables TRANSITIVE_LINKS_OUTPUT, EXTERNAL_LINKS_OUTPUT,
|
||
# and NON_EXISTENT_LINKS_OUTPUT. Where TRANSITIVE_LINKS_OUTPUT holds the transitive values of target/links found in those properties (recursively),
|
||
# EXTERNAL_LINKS_OUTPUT holds IMPORTED type targets found in the recursion, and NON_EXISTENT_LINKS_OUTPUT holds unknown/non-target values found
|
||
# (recursively).
|
||
#
|
||
# NON_EXISTENT_LINKS_OUTPUT will include directly linked files, linker flags, and other non-target values.
|
||
#
|
||
# > [!WARNING]
|
||
# > Properties supplied through PROPERTY_NAMES must be composed of mostly target names (e.g. LINK_LIBRARIES, MANUALLY_ADDED_DEPENDENCIES, etc.)
|
||
#
|
||
# - **CMAKE_BUILD_TARGET_NAME**: name of the target in the CMake system
|
||
# - **PROPERTY_NAMES**: list of properties containing other CMake target names to be read recursively
|
||
# - **TRANSITIVE_LINKS_OUTPUT**: name of output to write transitive links/dependencies in PARENT_SCOPE
|
||
# - **EXTERNAL_LINKS_OUTPUT**: name of output to write external (IMPORTED) links/dependencies in PARENT_SCOPE
|
||
# - **NON_EXISTENT_LINKS_OUTPUT**: name of output to write non-target links/dependencies in PARENT_SCOPE
|
||
####
|
||
function(recurse_target_properties CMAKE_BUILD_TARGET_NAME PROPERTY_NAMES TRANSITIVE_LINKS_OUTPUT EXTERNAL_LINKS_OUTPUT NON_EXISTENT_LINKS_OUTPUT)
|
||
# Recursive leafs:
|
||
# 1. This is not a known target
|
||
# 2. This target has not further links
|
||
|
||
# If the current item is not a target, tell the parent that this is a nonexistent entity
|
||
if (NOT TARGET "${CMAKE_BUILD_TARGET_NAME}")
|
||
set("${NON_EXISTENT_LINKS_OUTPUT}" "${CMAKE_BUILD_TARGET_NAME}" PARENT_SCOPE)
|
||
set("${TRANSITIVE_LINKS_OUTPUT}" PARENT_SCOPE)
|
||
set("${EXTERNAL_LINKS_OUTPUT}" PARENT_SCOPE)
|
||
return()
|
||
endif()
|
||
# If the target is imported, tell the parent that this is an external target
|
||
get_target_property(IMPORTED_TARGET "${CMAKE_BUILD_TARGET_NAME}" IMPORTED)
|
||
if (IMPORTED_TARGET)
|
||
set("${NON_EXISTENT_LINKS_OUTPUT}" PARENT_SCOPE)
|
||
set("${TRANSITIVE_LINKS_OUTPUT}" PARENT_SCOPE)
|
||
set("${EXTERNAL_LINKS_OUTPUT}" "${CMAKE_BUILD_TARGET_NAME}" PARENT_SCOPE)
|
||
return()
|
||
endif()
|
||
# Read all supplied properties and add them to the list of items to recurse
|
||
set(PROPERTY_LIST)
|
||
foreach(PROPERTY_NAME IN LISTS PROPERTY_NAMES)
|
||
get_target_property(PROPERTY_LIST_LOOPED "${CMAKE_BUILD_TARGET_NAME}" "${PROPERTY_NAME}")
|
||
if (PROPERTY_LIST_LOOPED)
|
||
list(APPEND PROPERTY_LIST ${PROPERTY_LIST_LOOPED})
|
||
endif()
|
||
endforeach()
|
||
list(REMOVE_DUPLICATES PROPERTY_LIST)
|
||
# When there are no other link libraries below this one, return current target as the singular dependency
|
||
if (NOT PROPERTY_LIST)
|
||
set("${NON_EXISTENT_LINKS_OUTPUT}" PARENT_SCOPE)
|
||
set("${TRANSITIVE_LINKS_OUTPUT}" "${CMAKE_BUILD_TARGET_NAME}" PARENT_SCOPE)
|
||
set("${EXTERNAL_LINKS_OUTPUT}" PARENT_SCOPE)
|
||
return()
|
||
endif()
|
||
set(PREVIOUSLY_RECURSED ${ARGN} ${CMAKE_BUILD_TARGET_NAME})
|
||
|
||
# Look through each current link library using a recursive call
|
||
set(RECURSED_TRANSITIVE)
|
||
set(RECURSED_UNKNOWN)
|
||
set(RECURSED_EXTERNAL)
|
||
foreach(LINK IN LISTS PROPERTY_LIST)
|
||
unset(INTERNAL_TRANSITIVE)
|
||
unset(INTERNAL_UNKNOWN)
|
||
# Prevent redundant recursion
|
||
if (NOT LINK IN_LIST PREVIOUSLY_RECURSED AND NOT LINK STREQUAL "")
|
||
fprime_cmake_ASSERT("'${LINK}' is a null dependency of '${CMAKE_BUILD_TARGET_NAME}'" LINK)
|
||
# Recurse through each link and append the recursively determined additions to the list
|
||
# while ensuring there are no duplicates
|
||
recurse_target_properties("${LINK}" "${PROPERTY_NAMES}" INTERNAL_TRANSITIVE INTERNAL_EXTERNAL INTERNAL_UNKNOWN ${PREVIOUSLY_RECURSED})
|
||
# The current link must occur in one list or the other
|
||
fprime_cmake_ASSERT("'${LINK}' must appear in '${INTERNAL_TRANSITIVE}' or '${INTERNAL_UNKNOWN}'"
|
||
LINK IN_LIST INTERNAL_TRANSITIVE OR LINK IN_LIST INTERNAL_UNKNOWN OR LINK IN_LIST INTERNAL_EXTERNAL)
|
||
# Append the lists to the aggregated output
|
||
list(APPEND RECURSED_TRANSITIVE ${INTERNAL_TRANSITIVE})
|
||
list(APPEND RECURSED_UNKNOWN ${INTERNAL_UNKNOWN})
|
||
list(APPEND RECURSED_EXTERNAL ${INTERNAL_EXTERNAL})
|
||
list(REMOVE_DUPLICATES RECURSED_TRANSITIVE)
|
||
list(REMOVE_DUPLICATES RECURSED_UNKNOWN)
|
||
list(REMOVE_DUPLICATES RECURSED_EXTERNAL)
|
||
# Update previously touched modules
|
||
list(APPEND PREVIOUSLY_RECURSED ${INTERNAL_TRANSITIVE} ${INTERNAL_UNKNOWN} ${INTERNAL_EXTERNAL})
|
||
list(REMOVE_DUPLICATES PREVIOUSLY_RECURSED)
|
||
endif()
|
||
endforeach()
|
||
# Return the results of this stage of the recursion
|
||
set("${NON_EXISTENT_LINKS_OUTPUT}" ${RECURSED_UNKNOWN} PARENT_SCOPE)
|
||
set("${TRANSITIVE_LINKS_OUTPUT}" ${CMAKE_BUILD_TARGET_NAME} ${RECURSED_TRANSITIVE} PARENT_SCOPE)
|
||
set("${EXTERNAL_LINKS_OUTPUT}" ${RECURSED_EXTERNAL} PARENT_SCOPE)
|
||
endfunction()
|
||
|
||
####
|
||
# Function `fprime__internal_target_interceptor`:
|
||
#
|
||
# A function that intercepts calls to target_* functions and translates the scope from PUBLIC to INTERFACE when the
|
||
# target is an INTERFACE target.
|
||
#
|
||
# - **FUNCTION_NAME**: name of the target_* function to intercept
|
||
# - **BUILD_TARGET_NAME**: name of the target to set
|
||
# - **SCOPE**: scope of the target to intercept and change
|
||
# - **ARGN**: arguments to pass to the target_* function
|
||
####
|
||
function(fprime__internal_target_interceptor FUNCTION_NAME BUILD_TARGET_NAME SCOPE)
|
||
# Get the target type
|
||
get_target_property(TARGET_TYPE "${BUILD_TARGET_NAME}" TYPE)
|
||
# If the target is an INTERFACE_LIBRARY, change the scope to INTERFACE
|
||
if (TARGET_TYPE STREQUAL "INTERFACE_LIBRARY" AND SCOPE STREQUAL "PUBLIC")
|
||
set(SCOPE INTERFACE)
|
||
endif()
|
||
# Call the target_* function with the new scope
|
||
cmake_language(CALL "${FUNCTION_NAME}" "${BUILD_TARGET_NAME}" "${SCOPE}" ${ARGN})
|
||
endfunction()
|
||
####
|
||
# Function `fprime_target_link_libraries`:
|
||
#
|
||
# This function wraps `target_link_libraries` to ensure that PUBLIC scope additions translate to INTERFACE when
|
||
# the target is an INTERFACE target. This makes it easier to deal with INTERFACE targets.
|
||
#
|
||
# See: target_link_libraries
|
||
####
|
||
function(fprime_target_link_libraries BUILD_TARGET_NAME SCOPE)
|
||
fprime__internal_target_interceptor("target_link_libraries" "${BUILD_TARGET_NAME}" "${SCOPE}" ${ARGN})
|
||
endfunction()
|
||
|
||
####
|
||
# Function `fprime_target_include_directories`:
|
||
#
|
||
# This function wraps `target_include_directories` to ensure that PUBLIC scope additions translate to INTERFACE when
|
||
# the target is an INTERFACE target. This makes it easier to deal with INTERFACE targets.
|
||
#
|
||
# See: target_include_directories
|
||
#
|
||
####
|
||
function(fprime_target_include_directories BUILD_TARGET_NAME SCOPE)
|
||
fprime__internal_target_interceptor("target_include_directories" "${BUILD_TARGET_NAME}" "${SCOPE}" ${ARGN})
|
||
endfunction()
|
||
|
||
####
|
||
# Function `fprime_target_dependencies`:
|
||
#
|
||
# Adds dependencies to the supplied BUILD_TARGET_NAME properly handling scope (see fprime_target_link_libraries). Adding a dependency
|
||
# involves 2 steps:
|
||
# 1. Adding a link dependency from BUILD_TARGET_NAME to supplied dependencies
|
||
# 2. Append supplied dependencies to the FPRIME_DEPENDENCIES property of BUILD_TARGET_NAME
|
||
#
|
||
# - **BUILD_TARGET_NAME**: name of the target to add dependencies to
|
||
# - **SCOPE**: scope of the target to intercept and change from PUBLIC to INTERFACE for INTERFACE_LIBRARY targets targets
|
||
# - **ARGN**: dependencies to add to the target
|
||
####
|
||
function(fprime_target_dependencies BUILD_TARGET_NAME SCOPE)
|
||
fprime_target_link_libraries("${BUILD_TARGET_NAME}" "${SCOPE}" ${ARGN})
|
||
append_list_property("${ARGN}" TARGET "${BUILD_TARGET_NAME}" PROPERTY FPRIME_DEPENDENCIES)
|
||
endfunction()
|