From 0dd9fc1390bb224d01a88a6c30097f446812fce8 Mon Sep 17 00:00:00 2001
From: Reinhard Biegel <reinhard.biegel@in-tech.com>
Date: Tue, 7 Sep 2021 11:35:01 +0200
Subject: [PATCH] mod(CMake): Reworked OSI linkage handling

In CMakeLists.txt, add_openpass_target() now supports a parameter to
`LINKOSI`, specifying shared or static linking of OSI and protobuf.

Signed-off-by: Reinhard Biegel <reinhard.biegel@in-tech.com>
---
 cmake/FindOSI.cmake                           | 149 +++++++++++-------
 cmake/FindProtobuf.cmake                      |  79 ++++++++++
 cmake/HelperMacros.cmake                      |  54 +++++--
 cmake/global.cmake                            |   1 -
 .../Algorithm_FmuWrapper/CMakeLists.txt       |   5 +-
 .../Algorithm_FmuWrapper/CMakeLists.txt       |   5 +-
 6 files changed, 208 insertions(+), 85 deletions(-)
 create mode 100644 cmake/FindProtobuf.cmake

diff --git a/cmake/FindOSI.cmake b/cmake/FindOSI.cmake
index c754d7eb9..6b5552741 100644
--- a/cmake/FindOSI.cmake
+++ b/cmake/FindOSI.cmake
@@ -1,85 +1,112 @@
 ################################################################################
 # Copyright (c) 2021 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
 #
-# 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.
+# This program and the accompanying materials are made
+# available under the terms of the Eclipse Public License 2.0
+# which is available at https://www.eclipse.org/legal/epl-2.0/
 #
 # SPDX-License-Identifier: EPL-2.0
 ################################################################################
 #
-# - Find OSI
-# Find the OSI includes and library
+# Find Package Adapter for OSI (Open Simulation Interface)
 #
-#  OSI_INCLUDE_DIR - Where to find OSI includes
-#  OSI_LIBRARIES   - List of libraries when using OSI
-#  OSI_FOUND       - True if OSI was found
+# Creates the follwoing imported targets (if available):
+# - osi::shared
+# - osi::static
+# - osi::pic
 
-IF(OSI_INCLUDE_DIR)
-  SET(OSI_FIND_QUIETLY TRUE)
-ENDIF(OSI_INCLUDE_DIR)
-
-FIND_PATH(OSI_INCLUDE_DIR "osi3/osi_version.pb.h"
-  PATHS
-  ${PREFIX_PATH}
-  $ENV{OSI_HOME}/include
-  $ENV{EXTERNLIBS}/OSI/include
-  /usr/local/include
-  /usr/include
-  PATH_SUFFIXES include
-   DOC "OSI - Headers"
+set(OSI_SHARED_NAMES
+  open_simulation_interface.lib
+  libopen_simulation_interface.dll.a
+  libopen_simulation_interface.so
 )
 
-SET(OSI_NAMES
-    osi3/open_simulation_interface.lib
-    osi3/libopen_simulation_interface.dll.a
-    osi3/libopen_simulation_interface_pic.lib
-    osi3/libopen_simulation_interface.so
+set(OSI_STATIC_NAMES
+  open_simulation_interface_static.lib
+  libopen_simulation_interface_static.a
 )
 
-SET(OSI_DBG_NAMES
-    osi3/open_simulation_interfaced.lib
-    osi3/libopen_simulation_interfaced.dll.a
-    osi3/libopen_simulation_interface_picd.lib
+set(OSI_PIC_NAMES
+  open_simulation_interface_pic.lib
+  libopen_simulation_interface_pic.a
 )
 
-FIND_LIBRARY(OSI_LIBRARY NAMES ${OSI_NAMES}
+find_library(OSI_SHARED_LIBRARY NAMES ${OSI_SHARED_NAMES}
   PATHS
-  ${PREFIX_PATH}
-  $ENV{OSI_HOME}
-  $ENV{EXTERNLIBS}/OSI
-  /usr/local
-  /usr
-  PATH_SUFFIXES lib lib64
-  DOC "OSI - Library"
+    ${PREFIX_PATH}
+    /usr/local
+    /usr
+  PATH_SUFFIXES
+    lib/osi3
+    lib
+    lib64
 )
 
-INCLUDE(FindPackageHandleStandardArgs)
+find_library(OSI_STATIC_LIBRARY NAMES ${OSI_STATIC_NAMES}
+  PATHS
+    ${PREFIX_PATH}
+    /usr/local
+    /usr
+  PATH_SUFFIXES
+    lib/osi3
+    lib
+    lib64
+)
 
-IF(MSVC)
-  # VisualStudio needs a debug version
-  FIND_LIBRARY(OSI_LIBRARY_DEBUG NAMES ${OSI_DBG_NAMES}
-    PATHS
+find_library(OSI_PIC_LIBRARY NAMES ${OSI_PIC_NAMES}
+  PATHS
     ${PREFIX_PATH}
-    $ENV{OSI_HOME}/lib
-    $ENV{EXTERNLIBS}/OSI/lib
-    DOC "OSI - Library (Debug)"
-  )
+    /usr/local
+    /usr
+  PATH_SUFFIXES
+    lib/osi3
+    lib
+    lib64
+)
+
+if(OSI_SHARED_LIBRARY)
+  message(STATUS "Found OSI (shared): ${OSI_SHARED_LIBRARY}")
   
-  IF(OSI_LIBRARY_DEBUG AND OSI_LIBRARY)
-    SET(OSI_LIBRARIES optimized ${OSI_LIBRARY} debug ${OSI_LIBRARY_DEBUG})
-  ENDIF(OSI_LIBRARY_DEBUG AND OSI_LIBRARY)
+  get_filename_component(OSI_SHARED_LIBRARY_DIR "${OSI_SHARED_LIBRARY}" DIRECTORY)
 
-  FIND_PACKAGE_HANDLE_STANDARD_ARGS(OSI DEFAULT_MSG OSI_LIBRARY OSI_LIBRARY_DEBUG OSI_INCLUDE_DIR)
+  add_library(osi::shared IMPORTED SHARED)
+  set_target_properties(osi::shared
+                        PROPERTIES
+                          IMPORTED_LOCATION ${OSI_SHARED_LIBRARY}
+                          IMPORTED_IMPLIB ${OSI_SHARED_LIBRARY}
+                          INTERFACE_INCLUDE_DIRECTORIES ${OSI_SHARED_LIBRARY_DIR}/../../include
+                          INTERFACE_LINK_LIBRARIES protobuf::libprotobuf)
+else()
+  message(STATUS "Didn't find OSI (shared)")
+endif()
 
-  MARK_AS_ADVANCED(OSI_LIBRARY OSI_LIBRARY_DEBUG OSI_INCLUDE_DIR)
-  
-ELSE(MSVC)
-  # rest of the world
-  SET(OSI_LIBRARIES ${OSI_LIBRARY})
+if(OSI_STATIC_LIBRARY)
+  message(STATUS "Found OSI (static): ${OSI_STATIC_LIBRARY}")
+  get_filename_component(OSI_STATIC_LIBRARY_DIR "${OSI_STATIC_LIBRARY}" DIRECTORY)
+  add_library(osi::static IMPORTED STATIC)
+  set_target_properties(osi::static
+                        PROPERTIES
+                          IMPORTED_LOCATION ${OSI_STATIC_LIBRARY}
+                          INTERFACE_INCLUDE_DIRECTORIES ${OSI_STATIC_LIBRARY_DIR}/../../include
+                          INTERFACE_LINK_LIBRARIES protobuf::libprotobuf_static)
+else()
+  message(STATUS "Didn't find OSI (static)")
+endif()
 
-  FIND_PACKAGE_HANDLE_STANDARD_ARGS(OSI DEFAULT_MSG OSI_LIBRARY OSI_INCLUDE_DIR)
-  
-  MARK_AS_ADVANCED(OSI_LIBRARY OSI_INCLUDE_DIR)
-  
-ENDIF(MSVC)
+if(OSI_PIC_LIBRARY)
+  message(STATUS "Found OSI (pic): ${OSI_PIC_LIBRARY}")
+  get_filename_component(OSI_PIC_LIBRARY_DIR "${OSI_PIC_LIBRARY}" DIRECTORY)
+  add_library(osi::pic IMPORTED STATIC)
+  set_target_properties(osi::pic
+                        PROPERTIES
+                          IMPORTED_LOCATION ${OSI_PIC_LIBRARY}
+                          INTERFACE_INCLUDE_DIRECTORIES ${OSI_PIC_LIBRARY_DIR}/../../include
+                          INTERFACE_LINK_LIBRARIES protobuf::libprotobuf_static)
+else()
+  message(STATUS "Didn't find OSI (pic)")
+endif()
+
+
+unset(OSI_SHARED_LIBRARY)
+unset(OSI_STATIC_LIBRARY)
+unset(OSI_PIC_LIBRARY)
diff --git a/cmake/FindProtobuf.cmake b/cmake/FindProtobuf.cmake
new file mode 100644
index 000000000..5f4aca457
--- /dev/null
+++ b/cmake/FindProtobuf.cmake
@@ -0,0 +1,79 @@
+################################################################################
+# Copyright (c) 2021 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+#
+# This program and the accompanying materials are made
+# available under the terms of the Eclipse Public License 2.0
+# which is available at https://www.eclipse.org/legal/epl-2.0/
+#
+# SPDX-License-Identifier: EPL-2.0
+################################################################################
+#
+# find_package adapter for protobuf
+#
+# Original protpbuf CMake Config file doesn't provide static targets.
+#
+# Creates the follwoing imported targets (if available):
+# - protobuf::libprotobuf
+# - protobuf::libprotobuf_static
+
+set(PROTOBUF_SHARED_NAMES
+  protobuf.lib
+  libprotobuf.dll.a
+  libprotobuf.so
+)
+
+set(PROTOBUF_STATIC_NAMES
+  protobuf_static.lib
+  libprotobuf.a
+)
+
+find_library(PROTOBUF_SHARED_LIBRARY NAMES ${PROTOBUF_SHARED_NAMES}
+  PATHS
+    ${PREFIX_PATH}
+    /usr/local
+    /usr
+  PATH_SUFFIXES
+    lib
+    lib64
+)
+
+find_library(PROTOBUF_STATIC_LIBRARY NAMES ${PROTOBUF_STATIC_NAMES}
+  PATHS
+    ${PREFIX_PATH}
+    /usr/local
+    /usr
+  PATH_SUFFIXES
+    lib
+    lib64
+)
+
+if(PROTOBUF_SHARED_LIBRARY)
+  message(STATUS "Found protobuf (shared): ${PROTOBUF_SHARED_LIBRARY}")
+  get_filename_component(PROTOBUF_SHARED_LIBRARY_DIR "${PROTOBUF_SHARED_LIBRARY}" DIRECTORY)
+  add_library(protobuf::libprotobuf IMPORTED SHARED)
+  set_target_properties(protobuf::libprotobuf
+                        PROPERTIES
+                          IMPORTED_LOCATION ${PROTOBUF_SHARED_LIBRARY}
+                          IMPORTED_IMPLIB ${PROTOBUF_SHARED_LIBRARY}
+                          INTERFACE_COMPILE_DEFINITIONS PROTOBUF_USE_DLLS
+                          INTERFACE_INCLUDE_DIRECTORIES ${PROTOBUF_SHARED_LIBRARY_DIR}/../include
+                          INTERFACE_LINK_LIBRARIES pthread)
+else()
+  message(STATUS "Didn't find protobuf (shared)")
+endif()
+
+if(PROTOBUF_STATIC_LIBRARY)
+  message(STATUS "Found protobuf (static): ${PROTOBUF_STATIC_LIBRARY}")
+  get_filename_component(PROTOBUF_STATIC_LIBRARY_DIR "${PROTOBUF_STATIC_LIBRARY}" DIRECTORY)
+  add_library(protobuf::libprotobuf_static IMPORTED STATIC)
+  set_target_properties(protobuf::libprotobuf_static
+                        PROPERTIES
+                          IMPORTED_LOCATION ${PROTOBUF_STATIC_LIBRARY}
+                          INTERFACE_INCLUDE_DIRECTORIES ${PROTOBUF_STATIC_LIBRARY_DIR}/../include
+                          INTERFACE_LINK_LIBRARIES pthread)
+else()
+  message(STATUS "Didn't find protobuf (static)")
+endif()
+
+unset(PROTOBUF_SHARED_LIBRARY)
+unset(PROTOBUF_STATIC_LIBRARY)
diff --git a/cmake/HelperMacros.cmake b/cmake/HelperMacros.cmake
index ab8ec54d5..586e53b53 100644
--- a/cmake/HelperMacros.cmake
+++ b/cmake/HelperMacros.cmake
@@ -81,7 +81,7 @@ endif()
 #                         [INCDIRS <include-directories>]
 #                         [LIBRARIES <libraries>]
 #                         [UIS <qt_uis>]
-#                         [LINKOSI])
+#                         [LINKOSI [shared|static]]
 #                         [LINKGUI]
 #                         [FOLDER <category>]
 #                         [COMPONENT <gui|sim|core|bin|module>])
@@ -94,7 +94,7 @@ endif()
 #                         [INCDIRS <include-directories>]
 #                         [LIBRARIES <libraries>]
 #                         [UIS <qt_uis>]
-#                         [LINKOSI]
+#                         [LINKOSI [shared|static]]
 #                         [LINKGUI]
 #                         [FOLDER <category>]
 #                         [COMPONENT <gui|sim|core|bin|module>])
@@ -106,7 +106,7 @@ endif()
 #                         [INCDIRS <include-directories>]
 #                         [LIBRARIES <libraries>]
 #                         [UIS <qt_uis>]
-#                         [LINKOSI]
+#                         [LINKOSI [shared|static]]
 #                         [LINKGUI]
 #                         [DEFAULT_MAIN]
 #                         [SIMCORE_DEPS <dependencies>]
@@ -122,7 +122,7 @@ endif()
 # UIS            Qt UI files
 # INCDIRS        Additional include directories
 # LIBRARIES      Additional libraries to link
-# LINKOSI        Shortcut for adding OSI include directories and libraries (incl. protobuf)
+# 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
@@ -137,12 +137,13 @@ endif()
 #    - 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;LINKOSI;DEFAULT_MAIN" "NAME;TYPE;LINKAGE" "HEADERS;SOURCES;INCDIRS;LIBRARIES;UIS;SIMCORE_DEPS;RESOURCES;FOLDER;COMPONENT" ${ARGN})
+  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.")
@@ -274,16 +275,26 @@ function(add_openpass_target)
       Boost::headers
     )
 
-    if(${PARSED_ARG_LINKOSI})
-      target_include_directories(${PARSED_ARG_NAME} PRIVATE
-        ${OSI_INCLUDE_DIR}
-        protobuf::libprotobuf
-      )
+    # LINKOSI handling
 
-      target_link_libraries(${PARSED_ARG_NAME}
-        ${OSI_LIBRARIES}
-        protobuf::libprotobuf
-      )
+    # 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
@@ -305,12 +316,13 @@ function(add_openpass_target)
       if(DEFINED PARSED_ARG_LIBRARIES)
         list(APPEND DEPS ${PARSED_ARG_LIBRARIES})
       endif()
-      if(${PARSED_ARG_LINKOSI})
-        list(APPEND DEPS "${OSI_LIBRARIES}" protobuf::libprotobuf)
+      if(DEFINED PARSED_ARG_LINKOSI)
+        list(APPEND DEPS osi::${PARSED_ARG_LINKOSI} protobuf::libprotobuf)
       endif()
 
-      message(DEBUG "Locating shared library test dependencies...")
+      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}")
@@ -386,7 +398,15 @@ function(add_openpass_target)
         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}")
diff --git a/cmake/global.cmake b/cmake/global.cmake
index 3022d1f90..54aba6024 100644
--- a/cmake/global.cmake
+++ b/cmake/global.cmake
@@ -79,7 +79,6 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON)
 if(WITH_SIMCORE OR WITH_TESTS)
 
   find_package(Protobuf REQUIRED)
-  add_compile_definitions(PROTOBUF_USE_DLLS)
 
   find_package(OSI REQUIRED)
 
diff --git a/sim/src/components/Algorithm_FmuWrapper/CMakeLists.txt b/sim/src/components/Algorithm_FmuWrapper/CMakeLists.txt
index 1af3cc7b2..96248601f 100644
--- a/sim/src/components/Algorithm_FmuWrapper/CMakeLists.txt
+++ b/sim/src/components/Algorithm_FmuWrapper/CMakeLists.txt
@@ -42,13 +42,12 @@ add_openpass_target(
     ${FMILibrary_INCLUDE_DIR}/JM
     src/FmiImporter/include
     ../../core/opSimulation/modules/World_OSI
-    ${OSI_INCLUDE_DIR}
 
   LIBRARIES
     Qt5::Core
     ${FMILibrary_LIBRARY_DIR}
     Common
     CoreCommon
-    protobuf::libprotobuf
-    -L${OSI_INCLUDE_DIR}/../lib/osi3 open_simulation_interface_pic
+
+  LINKOSI static
 )
diff --git a/sim/tests/unitTests/components/Algorithm_FmuWrapper/CMakeLists.txt b/sim/tests/unitTests/components/Algorithm_FmuWrapper/CMakeLists.txt
index 0b5ef1adf..491d5433a 100644
--- a/sim/tests/unitTests/components/Algorithm_FmuWrapper/CMakeLists.txt
+++ b/sim/tests/unitTests/components/Algorithm_FmuWrapper/CMakeLists.txt
@@ -40,13 +40,12 @@ add_openpass_target(
     ${FMILibrary_INCLUDE_DIR}/FMI1
     ${FMILibrary_INCLUDE_DIR}/FMI2
     ${FMILibrary_INCLUDE_DIR}/JM
-    ${OSI_INCLUDE_DIR}
 
   LIBRARIES
     Qt5::Core
     Common
     ${FMILibrary_LIBRARY_DIR}
-    protobuf::libprotobuf
-    -L${OSI_INCLUDE_DIR}/../lib/osi3 open_simulation_interface_pic
+
+  LINKOSI static
 )
 
-- 
GitLab