import spdlog  = spdlog%lib{spdlog}
import simbody = libsimbody%lib{SimTKsimbody}

# Determine the OS name and system info to be used for the OSIM_OS_NAME and
# OSIM_SYS_INFO macros. Note that these are only used in Common/About.cpp so
# are only for informational purposes and thus not super important.

# Determine the OS name. Upstream uses CMAKE_SYSTEM_INFO so use values from
# https://cmake.org/cmake/help/latest/variable/CMAKE_SYSTEM_NAME.html.
#
switch $cxx.target.class, $cxx.target.system
{
  case 'linux'
    osname = 'Linux'
  case 'windows'
    osname = 'Windows' # Note: Includes Mingw-w64
  case 'bsd', 'freebsd'
    osname = 'FreeBSD'
  case 'bsd', 'openbsd'
    osname = 'OpenBSD'
  case 'bsd', 'netbsd'
    osname = 'NetBSD'
  case 'macos'
    osname = 'Darwin'
  case 'ios'
    osname = 'iOS'
}

# Upstream adds the OS version but it's not always available so skip it.
#
sysinfo = "$osname-$cxx.target.cpu"

# Headers and source files.
#
./: lib{osimLepton}: Vendors/lepton/include/hxx{**} \
                     Vendors/lepton/src/{hxx cxx}{*}

./: lib{osimCommon}: OpenSim/hxx{*}                                                    \
                     OpenSim/Common/{hxx cxx}{* -C3DFileAdapter -gcvspl -*DLL.cpp}    \
                     OpenSim/Common/{h c}{gcvspl}                                      \
                     OpenSim/Auxiliary/hxx{*}
libs{osimCommon}: OpenSim/Common/cxx{osimCommonDLL}

./: lib{osimSimulation}: OpenSim/Simulation/{hxx cxx}{** -**Test/** -*DLL.cpp}
libs{osimSimulation}: OpenSim/Simulation/cxx{osimSimulationDLL}

./: lib{osimActuators}: OpenSim/Actuators/{hxx cxx}{** -Test/** -*DLL.cpp}
libs{osimActuators}: OpenSim/Actuators/cxx{osimActuatorsDLL}

./: lib{osimAnalyses}: OpenSim/Analyses/{hxx cxx}{** -Test/** -*DLL.cpp}
libs{osimAnalyses}: OpenSim/Analyses/cxx{osimAnalysesDLL}

./: lib{osimTools}: OpenSim/Tools/{hxx cxx}{** -Test/** -*DLL.cpp}
libs{osimTools}: OpenSim/Tools/cxx{osimToolsDLL}

# Note: We don't enable CasADi support but do arrange for the sources to be
#       distributed. Note that we exclude all of MocoCasADiSolver/ except
#       MocoCasADiSolver.{h,cpp}.
#
./: lib{osimMoco}: OpenSim/Moco/{hxx cxx}{** -MocoCasADiSolver/* -Test/**}
lib{osimMoco}: OpenSim/Moco/MocoCasADiSolver/{hxx cxx}{MocoCasADiSolver}
lib{osimMoco}: OpenSim/Moco/MocoCasADiSolver/{hxx cxx}{* -MocoCasADiSolver}: \
  include = adhoc

# Build options.

compiler_info = [dir_path] $effect($cxx.path)
# Escape backslashes on Windows
compiler_info = [dir_path] $regex.replace($compiler_info, '\\', '\\\\')

common_poptions = -DOSIM_COMPILER_INFO=\""$compiler_info"\"     \
                  -DOSIM_OS_NAME="$osname"                      \
                  -DOSIM_SYS_INFO="$sysinfo"                    \
                  -DOSIM_VERSION="$(version.major).$(version.minor).$(version.patch)"

# @@ TODO Pretty sure this should be exported from lib{spdlog}. See
#    https://github.com/build2-packaging/spdlog/issues/ 22.
#
common_poptions += -DSPDLOG_COMPILED_LIB

# Work around Simbody bug https://github.com/simbody/simbody/issues/ 836.
#
# Note that without -DSWIG_PYTHON the problematic relops described in that
# issue are enabled in my case (g++ 14.2) because
# __cpp_lib_three_way_comparison is undefined in case of -std=c++17 because
# <=> was only introduced in C++20. But OpenSIM only supports C++17. With
# later standards I get compilation errors in spdlog. They could probably be
# fixed but OpenSIM very explicitly insists on C++17 in their
# CMakeLists.txt.
#
# This is obviously a dirty hack (we don't even build the bindings) which
# only "works by accident" but until the upstream issue(s) are addressed
# it's the least intrusive solution. @@ TODO Reconsider?
#
common_poptions += -DSWIG_PYTHON

# Always define NDEBUG because upstream contains incorrect assertions such as
# "i > 0" in Array<T>::remove(i) in Common/Array.h. The upstream build always
# adds -DNDEBUG except for debug builds which is probably why they haven't
# noticed this. The default CMAKE_BUILD_TYPE is ReleaseWithDebugInfo, so with
# -DNDEBUG and they seem to CI the release build. See upstream issue
# https://github.com/opensim-org/opensim-core/issues/ 4138.
#
common_poptions += -DNDEBUG

if ($cxx.target.class == 'windows')
  common_poptions += -D_CRT_SECURE_NO_DEPRECATE

# Note that none of the macros defined in the upstream build are referenced in
# Vendors/lepton.
#
Vendors/lepton/src/obj{*}:                                              \
  cxx.poptions += "-I$src_root/libopensim/Vendors/lepton/include"       \
                  -DOPENSIM_LEPTON_AUTHORS=\"Peter_Eastman\"            \
                  -DOSIMLEPTON_EXPORTS                                  \
                  $common_poptions

OpenSim/Common/obj{*}:                                                                  \
  cxx.poptions += "-I$src_root/libopensim"                                              \
                  -DOPENSIM_COMMON_AUTHORS=\"Clay_Anderson-Ayman_Habib-Peter_Loan\"     \
                  -DOPENSIM_COMMON_COPYRIGHT_YEARS=\"2005-2017\"                        \
                  -DOPENSIM_COMMON_LIBRARY_NAME=osimCommon                              \
                  -DOPENSIM_COMMON_MAJOR_VERSION="$version.major"                       \
                  -DOPENSIM_COMMON_MINOR_VERSION="$version.minor"                       \
                  -DOPENSIM_COMMON_BUILD_VERSION="$version.patch"                       \
                  -DOSIMCOMMON_EXPORTS                                                  \
                  $common_poptions

OpenSim/Simulation/obj{*}:                                                      \
  cxx.poptions +=                                                               \
    "-I$src_root/libopensim"                                                    \
    -DOPENSIM_SIMULATION_AUTHORS=\"Ajay_Seth-Michael_Sherman-Ayman_Habib\"      \
    -DOPENSIM_SIMULATION_COPYRIGHT_YEARS=\"2005-2017\"                          \
    -DOPENSIM_SIMULATION_LIBRARY_NAME=osimSimulation                            \
    -DOPENSIM_SIMULATION_MAJOR_VERSION="$version.major"                         \
    -DOPENSIM_SIMULATION_MINOR_VERSION="$version.minor"                         \
    -DOPENSIM_SIMULATION_BUILD_VERSION="$version.patch"                         \
    -DOSIMSIMULATION_EXPORTS                                                    \
    $common_poptions

OpenSim/Actuators/obj{*}:                                                               \
  cxx.poptions +=                                                                       \
    "-I$src_root/libopensim"                                                            \
    -DOPENSIM_ACTUATORS_AUTHORS=\"Ajay_Seth-Matt_Millard-Matt_Demers-Ayman_Habib\"      \
    -DOPENSIM_ACTUATORS_COPYRIGHT_YEARS=\"2005-2017\"                                   \
    -DOPENSIM_ACTUATORS_LIBRARY_NAME=osimActuators                                      \
    -DOPENSIM_ACTUATORS_MAJOR_VERSION="$version.major"                                  \
    -DOPENSIM_ACTUATORS_MINOR_VERSION="$version.minor"                                  \
    -DOPENSIM_ACTUATORS_BUILD_VERSION="$version.patch"                                  \
    -DOSIMACTUATORS_EXPORTS                                                             \
    $common_poptions

OpenSim/Analyses/obj{*}:                                                \
  cxx.poptions +=                                                       \
    "-I$src_root/libopensim"                                            \
    -DOPENSIM_ANALYSES_AUTHORS=\"Clay_Anderson-Ayman_Habib-Peter_Loan\" \
    -DOPENSIM_ANALYSES_COPYRIGHT_YEARS=\"2005-2017\"                    \
    -DOPENSIM_ANALYSES_LIBRARY_NAME=osimAnalyses                        \
    -DOPENSIM_ANALYSES_MAJOR_VERSION="$version.major"                   \
    -DOPENSIM_ANALYSES_MINOR_VERSION="$version.minor"                   \
    -DOPENSIM_ANALYSES_BUILD_VERSION="$version.patch"                   \
    -DOSIMANALYSES_EXPORTS                                              \
    $common_poptions

OpenSim/Tools/obj{*}: cxx.poptions +=                                   \
                      "-I$src_root/libopensim"                          \
                      -DOPENSIM_TOOLS_AUTHORS=\"Frank_C_Anderson\"      \
                      -DOPENSIM_TOOLS_COPYRIGHT_YEARS=\"2005-2017\"     \
                      -DOPENSIM_TOOLS_LIBRARY_NAME=osimTools            \
                      -DOPENSIM_TOOLS_MAJOR_VERSION="$version.major"    \
                      -DOPENSIM_TOOLS_MINOR_VERSION="$version.minor"    \
                      -DOPENSIM_TOOLS_BUILD_VERSION="$version.patch"    \
                      -DOSIMTOOLS_EXPORTS                               \
                      $common_poptions

# Note: This library's upstream build has not been updated to use the common
# infrastructure used by the other libraries so there are some
# differences. Define the same macros as above (sans _AUTHORS) even though
# they're not present upstream to cover us when the upstream build is updated.
#
OpenSim/Moco/obj{*}:                            \
  cxx.poptions +=                               \
  "-I$src_root/libopensim"                      \
  -DOPENSIM_MOCO_AUTHORS=\"\"                   \
  -DOPENSIM_MOCO_COPYRIGHT_YEARS=\"2005-2017\"  \
  -DOPENSIM_MOCO_LIBRARY_NAME=osimMoco          \
  -DOPENSIM_MOCO_MAJOR_VERSION="$version.major" \
  -DOPENSIM_MOCO_MINOR_VERSION="$version.minor" \
  -DOPENSIM_MOCO_BUILD_VERSION="$version.patch" \
  -DOSIMMOCO_EXPORTS                            \
  $common_poptions                              \
  -DOPENSIM_MOCO_VERSION="$(version.major).$(version.minor).$(version.patch)"

# Static build support (config.bin.lib=static).
#
# Object.cpp uses the construct-on-first-use idiom (function-local statics)
# for its type-registry statics, same as Logger.cpp, eliminating the SIOF.
#
Vendors/lepton/src/objs{*}: cxx.poptions += -DLEPTON_BUILDING_SHARED_LIBRARY
Vendors/lepton/src/obja{*}: cxx.poptions += -DLEPTON_BUILDING_STATIC_LIBRARY

OpenSim/Common/objs{*}: cxx.poptions += -DOPENSIM_COMMON_TYPE=\"Shared\"
OpenSim/Common/obja{*}: cxx.poptions += -DOPENSIM_COMMON_TYPE=\"Static\" \
                                         -DOPENSIM_COMMON_TYPE_STATIC

OpenSim/Simulation/objs{*}: cxx.poptions += -DOPENSIM_SIMULATION_TYPE=\"Shared\"
OpenSim/Simulation/obja{*}: cxx.poptions += -DOPENSIM_SIMULATION_TYPE=\"Static\" \
                                             -DOPENSIM_SIMULATION_TYPE_STATIC

OpenSim/Actuators/objs{*}: cxx.poptions += -DOPENSIM_ACTUATORS_TYPE=\"Shared\"
OpenSim/Actuators/obja{*}: cxx.poptions += -DOPENSIM_ACTUATORS_TYPE=\"Static\" \
                                            -DOPENSIM_ACTUATORS_TYPE_STATIC

OpenSim/Analyses/objs{*}: cxx.poptions += -DOPENSIM_ANALYSES_TYPE=\"Shared\"
OpenSim/Analyses/obja{*}: cxx.poptions += -DOPENSIM_ANALYSES_TYPE=\"Static\" \
                                           -DOPENSIM_ANALYSES_TYPE_STATIC

OpenSim/Tools/objs{*}: cxx.poptions += -DOPENSIM_TOOLS_TYPE=\"Shared\"
OpenSim/Tools/obja{*}: cxx.poptions += -DOPENSIM_TOOLS_TYPE=\"Static\" \
                                        -DOPENSIM_TOOLS_TYPE_STATIC

OpenSim/Moco/objs{*}: cxx.poptions += -DOPENSIM_MOCO_TYPE=\"Shared\"
OpenSim/Moco/obja{*}: cxx.poptions += -DOPENSIM_MOCO_TYPE=\"Static\" \
                                       -DOPENSIM_MOCO_TYPE_STATIC

# Warnings: Upstream passes -Wall -Wextra -Wno-unused-local-typedefs in debug
# builds which results in pages of warnings. In non-debug builds they also
# pass -Wno-unused-variable -Wno-unused-parameter but still dozens of warnings
# are emitted.

if ($cxx.class == 'gcc')
{
  obj{*}: cxx.coptions += -funroll-loops                \
                          -Wno-unused-local-typedefs    \
                          -Wno-unused-variable          \
                          -Wno-unused-parameter

  # Upstream makes a lot of use of #pragma warning but it is not supported by
  # g++.
  #
  if ($cxx.target.class == 'windows')
    obj{*}: cxx.coptions += -Wno-unknown-pragmas
}

# "-Wshorten-64-to-32 helps us catch default warnings from Visual C++."
#
#    @@ Not sure what this means. Their check is just
#       `if(CMAKE_CXX_COMPILER_ID MATCHES Clang)`, so they're not checking for
#       clang-cl. So this will match many non-MSVC configs.
#
if ($cxx.id.type == 'clang')
  obj{*}: cxx.coptions += -Wshorten-64-to-32

# C4068: unknown pragma (from upstream build)
#
# Note: upstream passes /GS- which disables buffer overrun checking. Seems
# like a bad idea so leaving it enabled for now.
#
if ($cxx.class == 'msvc')
{
  obj{*}: cxx.coptions += /bigobj       \
                          /Oi           \
                          /wd4068
  obj{*}: cxx.poptions += -D_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
}

# Libraries
#
lib{osimCommon}:     $simbody $spdlog lib{osimLepton}
lib{osimSimulation}: lib{osimCommon}
lib{osimActuators}:  lib{osimSimulation}
lib{osimAnalyses}:   lib{osimActuators}
lib{osimTools}:      lib{osimAnalyses}
lib{osimMoco}:       lib{osimTools}

# Export options.
#
lib{osimLepton}: cxx.export.poptions = \
  "-I$src_root/libopensim/Vendors/lepton/include"

# NDEBUG: See above for background. We have to export it as well otherwise
# Array<T>::remove(0) will abort if Array<T> is not instantiated inside
# osimCommon. This can be demonstrated with the testStatesTrajectory test. If
# this export is remove that test will fail due to the bad assertion in
# Array<T>::remove().
#
#   @@ TODO Remove the NDEBUG export once upstream issue
#   https://github.com/opensim-org/opensim-core/issues/ 4138 has been
#   resolved.
#
lib{osimCommon}: cxx.export.poptions = "-I$src_base" \
                                       -DNDEBUG

if ($cxx.target.class == 'windows')
{
  liba{osimLepton}:     cxx.export.poptions += -DLEPTON_USE_STATIC_LIBRARIES
  liba{osimCommon}:     cxx.export.poptions += -DOPENSIM_COMMON_TYPE_STATIC    \
                                               -DLEPTON_USE_STATIC_LIBRARIES
  liba{osimSimulation}: cxx.export.poptions += -DOPENSIM_SIMULATION_TYPE_STATIC
  liba{osimActuators}:  cxx.export.poptions += -DOPENSIM_ACTUATORS_TYPE_STATIC
  liba{osimAnalyses}:   cxx.export.poptions += -DOPENSIM_ANALYSES_TYPE_STATIC
  liba{osimTools}:      cxx.export.poptions += -DOPENSIM_TOOLS_TYPE_STATIC
  liba{osimMoco}:       cxx.export.poptions += -DOPENSIM_MOCO_TYPE_STATIC
}

# Whole-archive linking for static builds.
#
# Each OpenSim library has a RegisterTypes_osim*.cpp TU with a file-scope
# static that self-registers all types. In shared builds this runs when the
# DLL is loaded. In static builds the linker strips unreferenced object files
# unless whole-archive is active, so the registration never runs. Setting
# bin.whole on the liba{} targets here ensures every consumer links all
# object files and registration happens.
#
liba{osimCommon osimSimulation osimActuators osimAnalyses osimTools osimMoco}: \
  bin.whole = true

lib{osimCommon}:     cxx.export.libs = $simbody $spdlog lib{osimLepton}
lib{osimSimulation}: cxx.export.libs = lib{osimCommon}
lib{osimActuators}:  cxx.export.libs = lib{osimSimulation}
lib{osimAnalyses}:   cxx.export.libs = lib{osimActuators}
lib{osimTools}:      cxx.export.libs = lib{osimAnalyses}
lib{osimMoco}:       cxx.export.libs = lib{osimTools}

# For pre-releases use the complete version to make sure they cannot
# be used in place of another pre-release or the final version. See
# the version module for details on the version.* variable values.
#
all_libs = osimLepton           \
           osimCommon           \
           osimSimulation       \
           osimActuators        \
           osimAnalyses         \
           osimTools            \
           osimMoco

if $version.pre_release
  lib{$all_libs}: bin.lib.version = "-$version.project_id"
else
  lib{$all_libs}: bin.lib.version = "-$version.major.$version.minor"

# By default, install into /usr/include/, recreating subdirectories, which
# will result in paths such as /usr/include/OpenSim/Common/Foo.h.
#
{hxx h}{*}:
{
  install         = include/
  install.subdirs = true
}

# Disable installation for certain headers.
#
OpenSim/Auxiliary/hxx{*}        \
OpenSim/hxx{version}@./OpenSim/ \
Vendors/hxx{*}:
{
  install         = false
  install.subdirs = false # Note: required
}

# CasADi is disabled. Keep MocoCasADiSolver.h (public stub) but do not install
# the CasADi implementation headers.
#
OpenSim/Moco/MocoCasADiSolver/hxx{*CasOC*}: install = false


# The Lepton headers need to be rearranged a bit.
#
Vendors/lepton/include/hxx{Lepton}@./Vendors/lepton/include/: \
  install = include/OpenSim/
Vendors/lepton/include/lepton/hxx{*}: \
  install = include/OpenSim/lepton/

# Note: Unfortunately, unlike the rest of the libraries, lepton headers are
# included as `lepton/Foo.h` (i.e., no `OpenSim` prefix), so it needs a special
# pkgconfig.include value.
#
lib{osimLepton}: cxx.pkgconfig.include = include/OpenSim/
