Skip to content

Commit

Permalink
Fix nasa#724, implement config-based target builds
Browse files Browse the repository at this point in the history
The existing build system built target executables grouped by toolchain
as a proxy for CPU architecture + machine options/flags.  The app binaries
would be built once and copied to any/all targets sharing that toolchain.

The side effect of doing this is that the application needs to be written
in an CPU-agnostic manner, performing its subscriptions and configurations
from runtime table data rather than hardcoded/fixed values.  Unfortunately
most apps are not coded that way, so workarounds were needed.

This changes the top level process to include the "platform" within this
target build logic, effectively treating different platform configs as
entirely different builds, even if they share the same toolchain file.

As a result, binaries will only be shared between targets that explicitly
set the "TGTx_PLATFORM" setting in targets.cmake to the same value.
  • Loading branch information
jphickey authored and Gerardo E. Cruz-Ortiz committed Jul 26, 2020
1 parent 4dc0329 commit 05eb82a
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 131 deletions.
2 changes: 1 addition & 1 deletion cmake/Makefile.sample
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
# Establish default values for critical variables. Any of these may be overridden
# on the command line or via the make environment configuration in an IDE
O ?= build
ARCH ?= native
ARCH ?= native/default_cpu1
BUILDTYPE ?= debug
INSTALLPREFIX ?= /exe
DESTDIR ?= $(O)
Expand Down
33 changes: 5 additions & 28 deletions cmake/arch_build.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -411,16 +411,6 @@ function(process_arch SYSVAR)
set(TGTLIST_DRV_${DRV})
endforeach()

# INCLUDE_REFACTOR: apps and the PSP like to #include cfe_platform_cfg.h -- they shouldn't
# This will become unnecessary when dependency refactoring is merged in, but for now
# they need to be able to find it. Remove the next line once refactoring is merged.
# Also do not do this if more than one CPU shares this architecture - this hack can only
# be done if a 1:1 mapping between cpus and architectures (so all apps are rebuilt per-cpu)
list(LENGTH TGTSYS_${SYSVAR} ARCHLEN)
if (ARCHLEN EQUAL 1)
include_directories(${CMAKE_BINARY_DIR}/cfe_core_default_${TGT${TGTSYS_${SYSVAR}}_NAME}/inc)
endif (ARCHLEN EQUAL 1)

# Process each PSP module that is referenced on this system architecture (any cpu)
foreach(PSPMOD ${TGTSYS_${SYSVAR}_PSPMODULES})
message(STATUS "Building PSP Module: ${PSPMOD}")
Expand Down Expand Up @@ -464,6 +454,9 @@ function(process_arch SYSVAR)
add_subdirectory(${${APP}_MISSION_DIR} apps/${APP})
endforeach()

# Actual core library is a subdirectory
add_subdirectory(${MISSION_SOURCE_DIR}/cfe/fsw/cfe-core cfe-core)

# If unit test is enabled, build a generic ut stub library for CFE
if (ENABLE_UNIT_TESTS)
add_subdirectory(${cfe-core_MISSION_DIR}/ut-stubs ut_cfe_core_stubs)
Expand All @@ -473,24 +466,8 @@ function(process_arch SYSVAR)
# Second Pass: Build cfe-core and link final target executable
foreach(TGTID ${TGTSYS_${SYSVAR}})

set(TGTNAME ${TGT${TGTID}_NAME})
set(TGTPLATFORM ${TGT${TGTID}_PLATFORM})
if(NOT TGTPLATFORM)
set(TGTPLATFORM "default" ${TGTNAME})
endif(NOT TGTPLATFORM)

string(REPLACE ";" "_" CFE_CORE_TARGET "cfe_core_${TGTPLATFORM}")
if (NOT TARGET ${CFE_CORE_TARGET})

# Generate wrapper file for the requisite cfe_platform_cfg.h file
generate_config_includefile("${CFE_CORE_TARGET}/inc/cfe_msgids.h" msgids.h ${TGTPLATFORM})
generate_config_includefile("${CFE_CORE_TARGET}/inc/cfe_platform_cfg.h" platform_cfg.h ${TGTPLATFORM})

# Actual core library is a subdirectory
add_subdirectory(${MISSION_SOURCE_DIR}/cfe/fsw/cfe-core ${CFE_CORE_TARGET})

endif (NOT TARGET ${CFE_CORE_TARGET})

set(TGTNAME ${TGT${TGTID}_NAME})

# Target to generate the actual executable file
add_subdirectory(cmake/target ${TGTNAME})

Expand Down
5 changes: 5 additions & 0 deletions cmake/cfe_generated_file.h.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@GENERATED_FILE_HEADER@

@GENERATED_FILE_CONTENT@

@GENERATED_FILE_TRAILER@
156 changes: 102 additions & 54 deletions cmake/global_functions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,49 @@
#
##################################################################

include(CMakeParseArguments)

##################################################################
#
# FUNCTION: generate_c_headerfile
#
# Generates a C header file in the build directory.
# First argument is the file name to write. All remaining arguments will be
# concatenated and written to the file.
#
function(generate_c_headerfile FILE_NAME)

# Determine the absolute location for this wrapper file
# If it is relative path then assume it is relative to ${CMAKE_BINARY_DIR}
# This should not write generated files to ${CMAKE_SOURCE_DIR}
if (NOT IS_ABSOLUTE "${FILE_NAME}")
set(FILE_NAME "${CMAKE_BINARY_DIR}/${FILE_NAME}")
endif (NOT IS_ABSOLUTE "${FILE_NAME}")

# Generate an include guard
get_filename_component(FILE_GUARD "${FILE_NAME}" NAME)
string(REGEX REPLACE "[^A-Za-z0-9]" "_" FILE_GUARD "${FILE_GUARD}")
string(TOUPPER "GENERATED_INCLUDE_${FILE_GUARD}" FILE_GUARD)
set(GENERATED_FILE_HEADER
"/* Generated header file. Do not edit */\n\n"
"#ifndef ${FILE_GUARD}\n"
"#define ${FILE_GUARD}\n\n"
)
set(GENERATED_FILE_TRAILER
"#endif /* ${FILE_GUARD} */\n"
)

# Use configure_file() to write the file, as this automatically
# has the logic to not update the timestamp on the file unless it changes.
string(REPLACE ";" "" GENERATED_FILE_CONTENT "${ARGN}")
string(REPLACE ";" "" GENERATED_FILE_HEADER "${GENERATED_FILE_HEADER}")
string(REPLACE ";" "" GENERATED_FILE_TRAILER "${GENERATED_FILE_TRAILER}")
configure_file(
"${CFE_SOURCE_DIR}/cmake/cfe_generated_file.h.in"
"${FILE_NAME}"
@ONLY)

endfunction(generate_c_headerfile)

##################################################################
#
Expand All @@ -21,58 +64,56 @@
# This also supports "stacking" multiple component files together by specifying more than one
# source file for the wrapper.
#
# In all cases, care must be used when updating such files -- if the timestamp is updated then
# everything that uses it will be rebuilt even if the content did not change. So the file content
# is checked against any pre-existing files, and if the content is the same then the update is
# skipped, preserving the original timestamp.
# This function now accepts named parameters:
# OUTPUT_DIRECTORY - where the generated file will be written
# FILE_NAME - the name of the file to write
# FALLBACK_FILE - if no files are found in "defs" using the name match, this file will be used instead.
# MATCH_SUFFIX - the suffix to match in the "defs" directory (optional)
# PREFIXES - a list of prefixes to match in the "defs" directory (optional)
#
function(generate_config_includefile DESTFILE SUFFIX)
function(generate_config_includefile)

# Determine the absolute location for this wrapper file
if (NOT IS_ABSOLUTE ${DESTFILE})
set(DESTFILE ${CMAKE_BINARY_DIR}/${DESTFILE})
endif (NOT IS_ABSOLUTE ${DESTFILE})

set(INCL_LIST)
set(DEST_CONTENTSTR "/* This is a wrapper file generated by the build system - DO NOT EDIT */\n")
foreach(SRC ${ARGN})
set(SRC_LOCAL_PATH "${MISSION_DEFS}/${SRC}_${SUFFIX}")
# only add this if not already included (prevent double inclusion) */
list(FIND INCL_LIST "${SRC_LOCAL_PATH}" INCL_INDX)
if (INCL_INDX LESS 0)
list(APPEND INCL_LIST "${SRC_LOCAL_PATH}")
if (EXISTS "${SRC_LOCAL_PATH}")
file(TO_NATIVE_PATH "${SRC_LOCAL_PATH}" SRC_NATIVE)
set(DEST_CONTENTSTR "${DEST_CONTENTSTR}#include \"${SRC_NATIVE}\"\n")
else()
set(DEST_CONTENTSTR "${DEST_CONTENTSTR}/* ${SRC_LOCAL_PATH} does not exist */\n")
endif (EXISTS "${SRC_LOCAL_PATH}")
endif (INCL_INDX LESS 0)
endforeach(SRC ${SRCFILES})
if (EXISTS "${DESTFILE}")
file(READ "${DESTFILE}" DEST_EXISTING)
else (EXISTS "${DESTFILE}")
set(DEST_EXISTING)
endif (EXISTS "${DESTFILE}")
if (NOT DEST_CONTENTSTR STREQUAL DEST_EXISTING)
file(WRITE "${DESTFILE}" "${DEST_CONTENTSTR}")
endif (NOT DEST_CONTENTSTR STREQUAL DEST_EXISTING)

# Create a copy of the generated file in a location where it can
# be found by an editor such as Eclipse. The user will still have to
# add the path to the IDEs include search path, but this makes it easier
# by at least putting them all in one spot. This is not used for the build.
get_filename_component(DESTBASENAME "${DESTFILE}" NAME)
if (MISSION_BINARY_DIR)
set(EDITOR_COPY_DIR ${MISSION_BINARY_DIR}/editor_inc)
else()
set(EDITOR_COPY_DIR ${CMAKE_BINARY_DIR}/editor_inc)
endif()

file(MAKE_DIRECTORY ${EDITOR_COPY_DIR})
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DESTFILE} ${EDITOR_COPY_DIR}/${DESTBASENAME})
cmake_parse_arguments(GENCONFIG_ARG "" "OUTPUT_DIRECTORY;FILE_NAME;FALLBACK_FILE;MATCH_SUFFIX" "PREFIXES" ${ARGN} )

set(WRAPPER_FILE_CONTENT)
set(ITEM_FOUND FALSE)

# Assemble a list of file names to test for
# Check for existence of a file in defs directory with an exact matching name
# Then Check for existence of file(s) with a matching prefix+suffix
set(CHECK_PATH_LIST "${MISSION_DEFS}/${GENCONFIG_ARG_FILE_NAME}")
if (GENCONFIG_ARG_MATCH_SUFFIX)
foreach(PREFIX ${GENCONFIG_ARG_PREFIXES} ${GENCONFIG_ARG_UNPARSED_ARGUMENTS})
list(APPEND CHECK_PATH_LIST "${MISSION_DEFS}/${PREFIX}_${GENCONFIG_ARG_MATCH_SUFFIX}")
endforeach()
endif(GENCONFIG_ARG_MATCH_SUFFIX)

# Check for existence of files, and add to content if present
# Note that all files are appended/concatenated together.
foreach(SRC_LOCAL_PATH ${CHECK_PATH_LIST})
if (EXISTS "${SRC_LOCAL_PATH}")
file(TO_NATIVE_PATH "${SRC_LOCAL_PATH}" SRC_NATIVE_PATH)
list(APPEND WRAPPER_FILE_CONTENT "#include \"${SRC_NATIVE_PATH}\"\n")
set(ITEM_FOUND TRUE)
else()
list(APPEND WRAPPER_FILE_CONTENT "/* ${SRC_LOCAL_PATH} does not exist */\n")
endif (EXISTS "${SRC_LOCAL_PATH}")
endforeach()

# If _no_ files were found in the above loop,
# then check for and use the fallback file.
# (if specified by the caller it should always exist)
if (NOT ITEM_FOUND AND GENCONFIG_ARG_FALLBACK_FILE)
file(TO_NATIVE_PATH "${GENCONFIG_ARG_FALLBACK_FILE}" SRC_NATIVE_PATH)
list(APPEND WRAPPER_FILE_CONTENT
"\n\n/* No configuration for ${GENCONFIG_ARG_FILE_NAME}, using fallback */\n"
"#include \"${GENCONFIG_ARG_FALLBACK_FILE}\"\n")
endif()

# Generate a header file
generate_c_headerfile("${GENCONFIG_ARG_OUTPUT_DIRECTORY}/${GENCONFIG_ARG_FILE_NAME}" ${WRAPPER_FILE_CONTENT})

endfunction(generate_config_includefile DESTFILE SUFFIX)
endfunction(generate_config_includefile)


##################################################################
Expand Down Expand Up @@ -106,21 +147,28 @@ function(read_targetconfig)
endif()
if (NOT DEFINED TGT${TGTID}_SYSTEM)
set(TGT${TGTID}_SYSTEM "cpu${TGTID}")
set(TGT${TGTID}_SYSTEM "${TGT${TGTID}_SYSTEM}" PARENT_SCOPE)
endif()
if (NOT DEFINED TGT${TGTID}_PLATFORM)
set(TGT${TGTID}_PLATFORM "default" "${TGT${TGTID}_NAME}")
set(TGT${TGTID}_PLATFORM "${TGT${TGTID}_PLATFORM}" PARENT_SCOPE)
endif()

if (SIMULATION)
# if simulation use simulation system architecture for all targets
set(CURRSYS "${SIMULATION}")
set(TOOLCHAIN_NAME "${SIMULATION}")
else (SIMULATION)
# get the target system arch identifier string
set(CURRSYS "${TGT${TGTID}_SYSTEM}")
set(TOOLCHAIN_NAME "${TGT${TGTID}_SYSTEM}")
endif (SIMULATION)

# make sure the string is safe for a variable name
string(REGEX REPLACE "[^A-Za-z0-9]" "_" SYSVAR "${CURRSYS}")
set(BUILD_CONFIG ${TOOLCHAIN_NAME} ${TGT${TGTID}_PLATFORM})

# convert to a the string which is safe for a variable name
string(REGEX REPLACE "[^A-Za-z0-9]" "_" SYSVAR "${BUILD_CONFIG}")

# save the unmodified name for future reference
set(SYSID_${SYSVAR} "${CURRSYS}" PARENT_SCOPE)
set(BUILD_CONFIG_${SYSVAR} "${BUILD_CONFIG}" PARENT_SCOPE)

# if the "global" applist is not empty, append to every CPU applist
if (MISSION_GLOBAL_APPLIST)
Expand Down
Loading

0 comments on commit 05eb82a

Please sign in to comment.