################################################################################
# Copyright (c) 2020-2021 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
# 2020 HLRS, University of Stuttgart
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
################################################################################
# @file HelperMacros.cmake
#
# @author Reinhard Biegel, in-tech GmbH
# @author René Paris, in-tech GmbH
# @author Uwe Woessner, HLRS
#
# Provides helper macros for defining build targets in the openPASS build ecosystem
#
# Macro to adjust the output directories of a target
function(openpass_adjust_output_dir targetname)
#MESSAGE("openpass_adjust_output_dir(${targetname}) : OPENPASS_DESTDIR = ${OPENPASS_DESTDIR}, ARGV1=${ARGV1}")
SET(MYPATH_POSTFIX )
# optional path-postfix specified?
IF(NOT "${ARGV1}" STREQUAL "")
IF("${ARGV1}" MATCHES "^/.*")
SET(MYPATH_POSTFIX "${ARGV1}")
ELSE()
SET(MYPATH_POSTFIX "/${ARGV1}")
ENDIF()
ENDIF()
# adjust
IF(CMAKE_CONFIGURATION_TYPES)
# generator supports configuration types
FOREACH(conf_type ${CMAKE_CONFIGURATION_TYPES})
STRING(TOUPPER "${conf_type}" upper_conf_type_str)
SET_TARGET_PROPERTIES(${ARGV0} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_${upper_conf_type_str} "${OPENPASS_DESTDIR}${MYPATH_POSTFIX}")
SET_TARGET_PROPERTIES(${ARGV0} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${upper_conf_type_str} "${OPENPASS_DESTDIR}${MYPATH_POSTFIX}")
SET_TARGET_PROPERTIES(${ARGV0} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${upper_conf_type_str} "${OPENPASS_DESTDIR}${MYPATH_POSTFIX}")
ENDFOREACH(conf_type)
ELSE(CMAKE_CONFIGURATION_TYPES)
# no configuration types - probably makefile generator
STRING(TOUPPER "${CMAKE_BUILD_TYPE}" upper_build_type_str)
SET_TARGET_PROPERTIES(${ARGV0} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_${upper_build_type_str} "${OPENPASS_DESTDIR}${MYPATH_POSTFIX}")
SET_TARGET_PROPERTIES(${ARGV0} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${upper_build_type_str} "${OPENPASS_DESTDIR}${MYPATH_POSTFIX}")
SET_TARGET_PROPERTIES(${ARGV0} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${upper_build_type_str} "${OPENPASS_DESTDIR}${MYPATH_POSTFIX}")
ENDIF(CMAKE_CONFIGURATION_TYPES)
endfunction(openpass_adjust_output_dir)
##
# WITH_COVERAGE extension
#
# When set, add compiler flags and make additional test targets available
#
# @author René Paris, in-tech GmbH
#
if(WITH_COVERAGE)
if(CMAKE_COMPILER_IS_GNUCXX)
include(TestCoverage)
set_coverage_compiler_flags()
else()
message(FATAL_ERROR "Generating code coverage is only supported for GNU gcc")
endif()
endif()
##
# Macro to add openPASS libraries, executables and tests
#
# @author Reinhard Biegel
# @author René Paris
#
# Usages:
# For building executables:
# add_openpass_target(NAME <target> TYPE executable
# SOURCES <sourcefiles>
# [HEADERS <headerfiles>]
# [INCDIRS <include-directories>]
# [LIBRARIES <libraries>]
# [UIS <qt_uis>]
# [LINKOSI [shared|static]]
# [LINKGUI]
# [FOLDER <category>]
# [COMPONENT <gui|sim|core|bin|module>])
#
# For building libraries:
# add_openpass_target(NAME <target> TYPE library
# LINKAGE <static|shared>
# SOURCES <sourcefiles>
# [HEADERS <headerfiles>]
# [INCDIRS <include-directories>]
# [LIBRARIES <libraries>]
# [UIS <qt_uis>]
# [LINKOSI [shared|static]]
# [LINKGUI]
# [FOLDER <category>]
# [COMPONENT <gui|sim|core|bin|module>])
#
# For building tests:
# add_openpass_target(NAME <target> TYPE test
# SOURCES <sourcefiles>
# [HEADERS <headerfiles>]
# [INCDIRS <include-directories>]
# [LIBRARIES <libraries>]
# [UIS <qt_uis>]
# [LINKOSI [shared|static]]
# [LINKGUI]
# [DEFAULT_MAIN]
# [SIMCORE_DEPS <dependencies>]
# [RESOURCES <directories>]
# [FOLDER <category>]
# [COMPONENT <gui|sim|core|bin|module>])
#
# NAME Specifies the target name, has to be unique
# TYPE Type of the target to build (executable, library or test)
# LINKAGE Specifies static or shared linkage for library targets
# SOURCES Source files
# HEADERS Header files
# UIS Qt UI files
# INCDIRS Additional include directories
# LIBRARIES Additional libraries to link
# LINKOSI Shortcut for adding OSI include directories and libraries (incl. protobuf) as 'static' or 'shared' (default).
# LINKGUI Shortcut for adding GUI Libraries
# DEFAULT_MAIN Links a simple main() implementation for running GTest
# SIMCORE_DEPS Adds dependencies on simulation core targets to a test
# RESOURCES List of directories to be copied to the test executable's location before test execution
# FOLDER The target will be sorted into that folder in your development environment
# tests will be sorted into tests/<category>
#
# In addtion to the parameters above:
# - For 'test' targets:
# - A test for building the test is added and the test will depend on that build target
# - gmock/gtest headers and include directories are added
# - gtest/gmock/pthread libraries are linked
# - Tests are excluded form the 'all' target
# - If DEFAULT_MAIN argument is provided, adds '--default-xml' to test executable command line arguments
# - PATH and LD_LIBRARY_PATH are set under Windows and Linux, respectively, so that test executables can resolve run-time dependencies.
# - General:
# - Target properties PROJECT_LABEL and OUTPUT_NAME are set to the target's name
# - Target property DEBUG_POSTFIX is set to CMAKE_DEBUG_POSTFIX
##
function(add_openpass_target)
cmake_parse_arguments(PARSED_ARG "LINKGUI;DEFAULT_MAIN" "NAME;TYPE;LINKAGE;LINKOSI" "HEADERS;SOURCES;INCDIRS;LIBRARIES;UIS;SIMCORE_DEPS;RESOURCES;FOLDER;COMPONENT" ${ARGN})
if(TARGET ${PARSED_ARG_NAME})
message(STATUS "Target '${PARSED_ARG_NAME}' already defined. Skipping.")
else()
# TODO: different categories of libraries can be placed in different directories
# GUI Components are installed in their own gui directory as an example
if("${PARSED_ARG_COMPONENT}" STREQUAL "gui")
set (DESTDIR ${SUBDIR_LIB_GUI})
elseif("${PARSED_ARG_COMPONENT}" STREQUAL "core")
set (DESTDIR ${SUBDIR_LIB_CORE})
elseif("${PARSED_ARG_COMPONENT}" STREQUAL "bin")
set (DESTDIR .)
elseif("${PARSED_ARG_COMPONENT}" STREQUAL "common")
set (DESTDIR ${SUBDIR_LIB_COMMON})
elseif("${PARSED_ARG_COMPONENT}" STREQUAL "module")
set (DESTDIR ${SUBDIR_LIB_MODULES})
else()
message(FATAL_ERROR "Unknown COMPONENT '${PARSED_ARG_COMPONENT}' please use any one of 'gui', 'core', 'bin', 'common', or 'module'.")
endif()
if("${PARSED_ARG_TYPE}" STREQUAL "library")
set(VALID_LINKAGES shared static)
if(NOT "${PARSED_ARG_LINKAGE}" IN_LIST VALID_LINKAGES)
message(FATAL_ERROR "Target type 'library' requires either 'shared' or 'static' LINKAGE")
else()
string(TOUPPER "${PARSED_ARG_LINKAGE}" PARSED_ARG_LINKAGE)
endif()
add_library(${PARSED_ARG_NAME} ${PARSED_ARG_LINKAGE} ${PARSED_ARG_HEADERS} ${PARSED_ARG_SOURCES} ${PARSED_ARG_UIS})
set_target_properties(${PARSED_ARG_NAME} PROPERTIES INSTALL_RPATH "\$ORIGIN/../${SUBDIR_LIB_EXTERNAL}:\$ORIGIN/../../${SUBDIR_LIB_EXTERNAL}:\$ORIGIN/../${SUBDIR_LIB_COMMON}:\$ORIGIN/../../${SUBDIR_LIB_COMMON}")
if("${PARSED_ARG_LINKAGE}" STREQUAL "SHARED")
if(WIN32)
install(TARGETS ${PARSED_ARG_NAME} RUNTIME DESTINATION "${DESTDIR}")
else()
install(TARGETS ${PARSED_ARG_NAME} LIBRARY DESTINATION "${DESTDIR}")
endif()
add_to_global_target_list(lib_target_list ${PARSED_ARG_NAME})
endif()
if(OPENPASS_ADJUST_OUTPUT)
openpass_adjust_output_dir(${PARSED_ARG_NAME} ${DESTDIR})
endif()
if(WIN32)
set_target_properties(${PARSED_ARG_NAME} PROPERTIES PREFIX "")
endif()
elseif("${PARSED_ARG_TYPE}" STREQUAL "executable")
if(DEFINED PARSED_ARG_LINKAGE)
message(WARNING "LINKAGE parameter isn't used by target type 'executable'")
endif()
add_executable(${PARSED_ARG_NAME} ${PARSED_ARG_HEADERS} ${PARSED_ARG_SOURCES} ${PARSED_ARG_UIS})
set_target_properties(${PARSED_ARG_NAME} PROPERTIES INSTALL_RPATH "\$ORIGIN/${SUBDIR_LIB_EXTERNAL}:\$ORIGIN/${SUBDIR_LIB_COMMON}")
install(TARGETS ${PARSED_ARG_NAME} RUNTIME DESTINATION "${DESTDIR}")
add_to_global_target_list(exe_target_list ${PARSED_ARG_NAME})
if(OPENPASS_ADJUST_OUTPUT)
openpass_adjust_output_dir(${PARSED_ARG_NAME} ${DESTDIR})
endif()
elseif("${PARSED_ARG_TYPE}" STREQUAL "test")
if(DEFINED PARSED_ARG_LINKAGE)
message(WARNING "LINKAGE parameter isn't used by target type 'executable'")
endif()
set(ADDITIONAL_TEST_ARGS)
if(${PARSED_ARG_DEFAULT_MAIN})
list(APPEND ADDITIONAL_TEST_ARGS "--default-xml")
list(APPEND PARSED_ARG_HEADERS
${TEST_PATH}/common/gtest/mainHelper.h
)
list(APPEND PARSED_ARG_SOURCES
${TEST_PATH}/common/gtest/mainHelper.cpp
${TEST_PATH}/common/gtest/unitTestMain.cpp
)
endif()
add_executable(${PARSED_ARG_NAME} EXCLUDE_FROM_ALL ${PARSED_ARG_HEADERS} ${PARSED_ARG_SOURCES} ${PARSED_ARG_UIS})
target_link_libraries(${PARSED_ARG_NAME}
GTest::gtest
GTest::gmock
pthread
)
add_test(NAME ${PARSED_ARG_NAME}_build COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ${PARSED_ARG_NAME})
add_test(NAME ${PARSED_ARG_NAME} COMMAND ${PARSED_ARG_NAME} ${ADDITIONAL_TEST_ARGS})
set_tests_properties(${PARSED_ARG_NAME} PROPERTIES DEPENDS ${PARSED_ARG_NAME}_build)
if(WITH_COVERAGE)
add_test_coverage_fastcov(NAME ${PARSED_ARG_NAME})
endif()
else()
message(FATAL_ERROR "Target type '${PARSED_ARG_TYPE}' is not supported.")
endif()
set_target_properties(${PARSED_ARG_NAME} PROPERTIES DEBUG_POSTFIX "${CMAKE_DEBUG_POSTFIX}")
set_target_properties(${PARSED_ARG_NAME} PROPERTIES PROJECT_LABEL "${PARSED_ARG_NAME}")
set_target_properties(${PARSED_ARG_NAME} PROPERTIES OUTPUT_NAME "${PARSED_ARG_NAME}")
if("${PARSED_ARG_FOLDER}" STREQUAL "")
set(PARSED_ARG_FOLDER ${FOLDER})
endif()
if("${PARSED_ARG_TYPE}" STREQUAL "test")
set_target_properties(${PARSED_ARG_NAME} PROPERTIES FOLDER "tests/${PARSED_ARG_FOLDER}")
else()
set_target_properties(${PARSED_ARG_NAME} PROPERTIES FOLDER "${PARSED_ARG_FOLDER}")
endif()
target_include_directories(${PARSED_ARG_NAME} PRIVATE
${PARSED_ARG_INCDIRS}
)
target_link_libraries(${PARSED_ARG_NAME}
${PARSED_ARG_LIBRARIES}
Qt5::Core
Boost::headers
)
# LINKOSI handling
# fallback to default if value is omitted
if("LINKOSI" IN_LIST PARSED_ARG_KEYWORDS_MISSING_VALUES)
set(PARSED_ARG_LINKOSI "shared")
endif()
if(DEFINED PARSED_ARG_LINKOSI)
# validate value
set(VALID_LINKOSI_VALUES "" "shared" "static")
if(NOT "${PARSED_ARG_LINKOSI}" IN_LIST VALID_LINKOSI_VALUES)
message(FATAL_ERROR "Invalid value for LINKOSI. Supported settings are '', 'shared' and 'static'")
endif()
# replace static with pic for library targets
if("${PARSED_ARG_TYPE}" STREQUAL "library" AND "${PARSED_ARG_LINKOSI}" STREQUAL "static")
set(PARSED_ARG_LINKOSI "pic")
endif()
target_link_libraries(${PARSED_ARG_NAME} osi::${PARSED_ARG_LINKOSI})
endif()
target_compile_options(${PARSED_ARG_NAME} PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:
-wd4251 -wd4335 -wd4250>)
if(${PARSED_ARG_LINKGUI})
qt5_use_modules(${PARSED_ARG_NAME} Core Gui Xml Widgets)
endif()
# locate shared library dependencies for test execution and add
# their location to PATH or LD_LIBRARY_PATH environment variable
if("${PARSED_ARG_TYPE}" STREQUAL "test")
set(DEPS)
set(DEP_PATHS)
if(DEFINED PARSED_ARG_SIMCORE_DEPS)
list(APPEND DEPS ${PARSED_ARG_SIMCORE_DEPS})
endif()
if(DEFINED PARSED_ARG_LIBRARIES)
list(APPEND DEPS ${PARSED_ARG_LIBRARIES})
endif()
if(DEFINED PARSED_ARG_LINKOSI)
list(APPEND DEPS osi::${PARSED_ARG_LINKOSI} protobuf::libprotobuf)
endif()
message(DEBUG "Locating shared library test dependencies for ${PARSED_ARG_NAME}")
foreach(DEP IN LISTS DEPS)
message(DEBUG "Locating ${DEP}...")
if(TARGET ${DEP})
set(DEP_PATH "")
message(DEBUG "Target dependency: ${DEP}")
# build dependencies
add_dependencies(${PARSED_ARG_NAME} ${DEP})
# test run dependencies
get_property(DEP_TARGET_TYPE TARGET ${DEP} PROPERTY TYPE)
if("${DEP_TARGET_TYPE}" STREQUAL "INTERFACE_LIBRARY")
message(DEBUG "Dependency is interface libarary. Ignoring.")
continue()
endif()
get_property(DEP_PATH TARGET ${DEP} PROPERTY IMPORTED_LOCATION)
if("${DEP_PATH}" STREQUAL "")
string(TOUPPER "${CMAKE_BUILD_TYPE}" TYPE)
get_property(DEP_PATH TARGET ${DEP} PROPERTY "IMPORTED_LOCATION_${TYPE}")
endif()
if("${DEP_PATH}" STREQUAL "")
get_property(DEP_PATH TARGET ${DEP} PROPERTY IMPORTED_LOCATION_RELEASE)
endif()
if("${DEP_PATH}" STREQUAL "")
get_property(DEP_PATH TARGET ${DEP} PROPERTY IMPORTED_LOCATION_DEBUG)
endif()
if("${DEP_PATH}" STREQUAL "")
get_property(DEP_PATH TARGET ${DEP} PROPERTY BINARY_DIR)
message(DEBUG "no IMPORTED_LOCATION defined, got BINARY_DIR: ${DEP_PATH}")
else()
message(DEBUG "IMPORTED_LOCATION: ${DEP_PATH}")
if(WIN32)
get_filename_component(DEP_EXT ${DEP_PATH} EXT)
if("${DEP_EXT}" STREQUAL ".dll.a")
string(REGEX REPLACE "\.a$" "" DEP_PATH_NO_A "${DEP_PATH}")
if(NOT EXISTS ${DEP_PATH_NO_A})
string(REGEX REPLACE "\/lib\/" "/bin/" DEP_PATH "${DEP_PATH_NO_A}")
endif()
endif()
if(EXISTS "${DEP_PATH}")
message(DEBUG "located shared library: ${DEP_PATH}")
get_filename_component(DEP_PATH ${DEP_PATH} DIRECTORY)
else()
message(DEBUG "unable to locate shared library")
continue()
endif()
endif()
endif()
else()
message(DEBUG "No target dependency: ${DEP}")
set(DEP_PATH "${DEP}")
if(WIN32)
get_filename_component(DEP_EXT "${DEP_PATH}" EXT)
if("${DEP_EXT}" STREQUAL ".dll.a")
string(REGEX REPLACE "\\.a$" "" DEP_PATH_NO_A "${DEP_PATH}")
if(EXISTS ${DEP_PATH_NO_A})
set(DEP_PATH "${DEP_PATH_NO_A}")
else()
string(REGEX REPLACE "/lib/" "/bin/" DEP_PATH "${DEP_PATH_NO_A}")
endif()
endif()
endif()
if(EXISTS "${DEP_PATH}")
message(DEBUG "located shared library: ${DEP_PATH}")
get_filename_component(DEP_PATH ${DEP_PATH} DIRECTORY)
else()
message(DEBUG "unable to locate shared library")
continue()
endif()
endif()
if(WIN32)
string(REGEX REPLACE "/" "\\\\" DEP_PATH "${DEP_PATH}")
endif()
list(APPEND DEP_PATHS "${DEP_PATH}")
endforeach()
list(REMOVE_DUPLICATES DEP_PATHS)
if(WIN32)
# try to move MSYS system folder to the end of the list
set(DEP_PATHS_NO_MSYS ${DEP_PATHS})
list(FILTER DEP_PATHS EXCLUDE REGEX "msys")
list(FILTER DEP_PATHS_MSYS INCLUDE REGEX "msys")
list(APPEND DEP_PATHS ${DEP_PATHS_MSYS})
list(JOIN DEP_PATHS "\\;" ADDITIONAL_PATHS)
set(CURRENT_PATH "$ENV{PATH}")
string(REGEX REPLACE "\;" "\\\;" CURRENT_PATH "${CURRENT_PATH}")
set_tests_properties(${PARSED_ARG_NAME} PROPERTIES ENVIRONMENT "PATH=${ADDITIONAL_PATHS}\;${CURRENT_PATH}")
else()
list(JOIN DEP_PATHS ":" ADDITIONAL_PATHS)
set_tests_properties(${PARSED_ARG_NAME} PROPERTIES ENVIRONMENT "LD_LIBRARY_PATH=${ADDITIONAL_PATHS}:$ENV{LD_LIBRARY_PATH}")
endif()
endif()
if(DEFINED PARSED_ARG_RESOURCES)
if("${PARSED_ARG_TYPE}" STREQUAL "test")
foreach(RES IN LISTS PARSED_ARG_RESOURCES)
add_custom_command(TARGET ${PARSED_ARG_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/${RES}
${CMAKE_CURRENT_BINARY_DIR}/${RES}
)
endforeach()
else()
message(WARNING "RESOURCES only used for 'test' openpass targets")
endif()
endif()
endif()
endfunction()
##
# Function to add a target's destination file to a global list
#
# Usage:
# add_to_global_target_list(<target-list> <target-name>)
#
# target-list: the global property to append the file to
# target-name: the name of the target to be added to the list
#
# NOTE:
# The generator expression won't be evaluated until usage of the target-list inside a scope that is able
# to parse the expression. Thus, the cmake policy CMP0087 has to be set to NEW for that scope.
#
function(add_to_global_target_list target_list target_name)
get_property(tmp GLOBAL PROPERTY ${target_list})
list(APPEND tmp $<TARGET_FILE:${target_name}>)
set_property(GLOBAL PROPERTY ${target_list} ${tmp})
endfunction()