cmake_minimum_required(VERSION 3.15...3.27)


set(CMAKE_LEGACY_CYGWIN_WIN32 0)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment target")
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment target")

project(g2o LANGUAGES CXX)

include(GNUInstallDirs)

# The library prefix
set(LIB_PREFIX g2o_)

set(g2o_C_FLAGS)
set(g2o_CXX_FLAGS)

set(G2O_LIB_VERSION   "2.1.0" CACHE STRING "g2o library version")
set(G2O_LIB_SOVERSION "2.1"   CACHE STRING "g2o library soversion")
set(G2O_VERSION 2.1.0)

# manually check for top-level project if CMake is older than 3.21
if(CMAKE_VERSION VERSION_LESS 3.21)
  string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PROJECT_IS_TOP_LEVEL)
endif()

if(PROJECT_IS_TOP_LEVEL)
  # default built type
  if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release CACHE STRING
            "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel."
            FORCE)
  endif()
endif()

# Allow the developer to select if Dynamic or Static libraries are built
option (BUILD_SHARED_LIBS "Build Shared Libraries (preferred and required for the g2o plugin system)" ON)
set (G2O_LIB_TYPE STATIC)
if (BUILD_SHARED_LIBS)
  set (G2O_LIB_TYPE SHARED)
else()
  set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()

# On the Mac platform, configure the RPATH as per the INSTALL, to
# avoid the problem of loading both the built and INSTALLed versions
# of the shared targets
if(APPLE)
  set(CMAKE_INSTALL_RPATH "")
  set(CMAKE_MACOSX_RPATH TRUE)
  # ignore deprecated GL
  add_definitions(-DGL_SILENCE_DEPRECATION)
endif(APPLE)

# Set the output directory for the build executables and libraries
set(g2o_RUNTIME_OUTPUT_DIRECTORY ${g2o_BINARY_DIR}/bin CACHE PATH "Target for the binaries")
if(WIN32)
  set(g2o_LIBRARY_OUTPUT_DIRECTORY ${g2o_BINARY_DIR}/bin CACHE PATH "Target for the libraries")
else(WIN32)
  set(g2o_LIBRARY_OUTPUT_DIRECTORY ${g2o_BINARY_DIR}/lib CACHE PATH "Target for the libraries")
endif(WIN32)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${g2o_LIBRARY_OUTPUT_DIRECTORY})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${g2o_LIBRARY_OUTPUT_DIRECTORY})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${g2o_RUNTIME_OUTPUT_DIRECTORY})

# Set standard installation directories
set(RUNTIME_DESTINATION ${CMAKE_INSTALL_BINDIR})
set(LIBRARY_DESTINATION ${CMAKE_INSTALL_LIBDIR})
set(ARCHIVE_DESTINATION ${CMAKE_INSTALL_LIBDIR})
set(INCLUDES_DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
set(INCLUDES_INSTALL_DIR ${INCLUDES_DESTINATION}/g2o)

# Set search directory for looking for our custom CMake scripts to
# look for SuiteSparse, QGLViewer, and Eigen3.
list(APPEND CMAKE_MODULE_PATH ${g2o_SOURCE_DIR}/cmake_modules)

# Detect OS and define macros appropriately
if(WIN32)
  add_definitions(-DWINDOWS)
  message(STATUS "Compiling on Windows")
elseif(CYGWIN)
  message(STATUS "Compiling on Cygwin")
  add_definitions(-DCYGWIN)
elseif(APPLE)
  add_definitions(-DUNIX)
  message(STATUS "Compiling on OSX")
elseif(UNIX)
  add_definitions(-DUNIX)
  message(STATUS "Compiling on Unix")
endif(WIN32)

# detect Android Cross Compiler
# based on android-cmake which sets the variable ANDROID for us
if(ANDROID)
  add_definitions(-DANDROID)
  message(STATUS "Cross compiling for Android")
endif()

# For building the CHOLMOD based solvers
option(G2O_USE_CHOLMOD "Build g2o with CHOLMOD support" ON)
find_package(SuiteSparse)
if (G2O_USE_CHOLMOD AND SuiteSparse_CHOLMOD_FOUND)
  message(STATUS "Enable support for Cholmod")
  set(CHOLMOD_FOUND TRUE)
else()
  message(STATUS "Disable support for Cholmod")
  set(CHOLMOD_FOUND FALSE)
endif()

# Options to control the LGPL libraries
option(G2O_USE_LGPL_LIBS "Build libraries which use LGPL code" TRUE)

# If the LGPL libraries are used, check if static or shared libraries are used and
# show a suitable message
if (G2O_USE_LGPL_LIBS)
  if (G2O_LIB_TYPE STREQUAL "STATIC")
    message(STATUS "Building LGPL code as a static library (affects license of the binary)")
  else()
    message(STATUS "Building LGPL code as a shared library")
  endif()
endif()

# Adapter for the legacy LGPL flags. Note there is an inconsistency with the old implementation.
if (BUILD_LGPL_SHARED_LIBS)
  if (G2O_LIB_TYPE STREQUAL "SHARED")
    set(G2O_USE_LGPL_LIBS TRUE)
  else()
    message(FATAL_ERROR "BUILD_LGPL_SHARED_LIBS is set to true but G2O_LIB_TYPE is set to STATIC")
  endif()
endif()

# For building the CSparse based solvers. Note this depends on an LGPL library.
option(G2O_USE_CSPARSE "Build g2o with CSParse support" ON)
find_package(CSparse)
if (${G2O_USE_CSPARSE} AND ${CSPARSE_FOUND} AND ${G2O_USE_LGPL_LIBS})
  message(STATUS "Enable support for CSparse")
else()
  message(STATUS "Disable support for CSparse")
  set(G2O_USE_CSPARSE FALSE)
endif()


# Eigen library parallelise itself, though, presumably due to performance issues
# OPENMP is experimental. We experienced some slowdown with it
option(G2O_USE_OPENMP "Build g2o with OpenMP support (EXPERIMENTAL)" OFF)
if(G2O_USE_OPENMP)
  find_package(OpenMP)
  if(OPENMP_FOUND)
    set (G2O_OPENMP 1)
    set(g2o_C_FLAGS "${g2o_C_FLAGS} ${OpenMP_C_FLAGS}")
    set(g2o_CXX_FLAGS "${g2o_CXX_FLAGS} -DEIGEN_DONT_PARALLELIZE ${OpenMP_CXX_FLAGS}")
    message(STATUS "Compiling with OpenMP support")
  endif(OPENMP_FOUND)
endif(G2O_USE_OPENMP)

# OpenGL is used in the draw actions for the different types, as well
# as for creating the GUI itself
set(OpenGL_GL_PREFERENCE "GLVND")
find_package(OpenGL)

# If OpenGL was found, use the import target if available. If not, use old-style includes
option(G2O_USE_OPENGL "Build g2o with OpenGL support for visualization" ON)
set(G2O_HAVE_OPENGL 0)
if (OPENGL_FOUND AND G2O_USE_OPENGL)
  if (TARGET OpenGL::GL)
    set(G2O_OPENGL_TARGET "OpenGL::GL;OpenGL::GLU")
  else()
    set(G2O_OPENGL_TARGET "${OPENGL_LIBRARIES}")
    include_directories(${OPENGL_INCLUDE_DIR})
  endif()
  set(G2O_HAVE_OPENGL 1)
  message(STATUS "Compiling with OpenGL support")
  #message(WARNING G2O_OPENGL_TARGET=${G2O_OPENGL_TARGET})
endif()

# For building the GUI
find_package(QGLViewer)

# Logging library
option(G2O_USE_LOGGING "Try to use spdlog for logging" ON)
set(G2O_HAVE_LOGGING 0)
if (G2O_USE_LOGGING)
  find_package(spdlog 1.6 QUIET)
  if (TARGET spdlog::spdlog OR TARGET spdlog::spdlog_header_only)
    set(G2O_HAVE_LOGGING 1)
    message(STATUS "Compiling with logging support")
  endif()
endif()

# Handle building the built in types. There is a fair degree of
# granularity and dependency in the types supported.

# 2D types
option(G2O_BUILD_SLAM2D_TYPES "Build SLAM2D types" ON)
option(G2O_BUILD_SLAM2D_ADDON_TYPES "Build SLAM2D addon types" ON)
option(G2O_BUILD_DATA_TYPES "Build SLAM2D data types" ON)
option(G2O_BUILD_SCLAM2D_TYPES "Build SCLAM2D types" ON)

# Process the arguments. If G2O_BUILD_SLAM2D_TYPES is disabled, forceably
# disable all types. Otherwise, update messages on the types enabled.
if(G2O_BUILD_SLAM2D_TYPES)
  string(APPEND supported_types_message " slam2d")
  # Make sure that we can't activate both types
  if(G2O_BUILD_SLAM2D_ADDON_TYPES)
    string(APPEND supported_types_message " slam2d (addons)")
  endif()
  if(G2O_BUILD_DATA_TYPES)
    string(APPEND supported_types_message " data")
  endif()
  if(G2O_BUILD_SCLAM2D_TYPES)
    string(APPEND supported_types_message " sclam2d")
  endif()
else()
  set(G2O_BUILD_SLAM2D_ADDON_TYPES OFF)
  set(G2O_BUILD_DATA_TYPES OFF)
  set(G2O_BUILD_SCLAM2D_TYPES OFF)
endif()

# 3D types
option(G2O_BUILD_SLAM3D_TYPES "Build SLAM 3D types" ON)
option(G2O_BUILD_SLAM3D_ADDON_TYPES "Build SLAM 3D addon types" ON)
option(G2O_BUILD_SBA_TYPES "Build SLAM3D SBA types" ON)
option(G2O_BUILD_ICP_TYPES "Build SLAM3D ICP types" ON)
option(G2O_BUILD_SIM3_TYPES "Build SLAM3D sim3 types" ON)

# Process the arguments. If G2O_BUILD_SLAM3D_TYPES is disabled, forceably
# disable all the types. Enable SBA if it's not enabled but one of it's
# dependents is enabled.
if(G2O_BUILD_SLAM3D_TYPES)
  string(APPEND supported_types_message " slam3d")
  message(STATUS "Compiling SLAM 3D types")
  if (G2O_BUILD_SLAM3D_ADDON_TYPES)
    string(APPEND supported_types_message " slam3d (addons)")
  endif()
  if (G2O_BUILD_SBA_TYPES)
    string(APPEND supported_types_message " sba")
  endif()
  if (G2O_BUILD_ICP_TYPES)
    if(NOT G2O_BUILD_SBA_TYPES)
      message(WARNING "ICP types requested but SBA types not enabled; enabling SBA")
      set(G2O_BUILD_SBA_TYPES ON)
      string(APPEND supported_types_message " sba")
    endif()
    string(APPEND supported_types_message " icp")
  endif()
  if (G2O_BUILD_SIM3_TYPES)
    if(NOT G2O_BUILD_SBA_TYPES)
      message(WARNING "SIM3 types requested but SBA types not enabled; enabling SBA")
      set(G2O_BUILD_SBA_TYPES ON)
      string(APPEND supported_types_message " sba")
    endif()
    string(APPEND supported_types_message " sim3")
  endif()
else()
  set(G2O_BUILD_SLAM3D_ADDON_TYPES OFF)
  set(G2O_BUILD_SBA_TYPES OFF)
  set(G2O_BUILD_ICP_TYPES OFF)
  set(G2O_BUILD_SIM3_TYPES OFF)
endif()

if(DEFINED supported_types_message)
  message(STATUS "Compiling built in types" ${supported_types_message})
else()
  message(STATUS "Compiling with no built in types enabled")
endif()

# shall we build the core apps using the library
option(G2O_BUILD_APPS "Build g2o apps" ON)
if(G2O_BUILD_APPS)
  message(STATUS "Compiling g2o apps")
endif(G2O_BUILD_APPS)

include(CMakeDependentOption)
CMAKE_DEPENDENT_OPTION(G2O_BUILD_LINKED_APPS "Build apps linked with the libraries (no plugin system)" OFF
  "G2O_BUILD_APPS" OFF)

# shall we build the examples
option(G2O_BUILD_EXAMPLES "Build g2o examples" ON)
if(G2O_BUILD_EXAMPLES)
  message(STATUS "Compiling g2o examples")
endif(G2O_BUILD_EXAMPLES)

# Start of SSE* autodetect code
# (borrowed from MRPT CMake scripts, BSD)
option(DO_SSE_AUTODETECT "Enable autodetection of SSE* CPU sets and enable their use in optimized code" ON)
if(NOT EXISTS "/proc/cpuinfo")
	set(DO_SSE_AUTODETECT OFF)
endif()
if (DO_SSE_AUTODETECT)
  file(READ "/proc/cpuinfo" G2O_CPU_INFO)
endif()

# Macro for each SSE* var: Invoke with name in uppercase:
macro(DEFINE_SSE_VAR  _setname)
	string(TOLOWER ${_setname} _set)
	if (DO_SSE_AUTODETECT)
		# Automatic detection:
		set(CMAKE_G2O_HAS_${_setname} 0)
		if (${G2O_CPU_INFO} MATCHES ".*${_set}.*")
			set(CMAKE_G2O_HAS_${_setname} 1)
		endif()
	else (DO_SSE_AUTODETECT)
		# Manual:
		option("DISABLE_${_setname}" "Forces compilation WITHOUT ${_setname} extensions" OFF)
		mark_as_advanced("DISABLE_${_setname}")
		set(CMAKE_G2O_HAS_${_setname} 0)
		if (NOT DISABLE_${_setname})
			set(CMAKE_G2O_HAS_${_setname} 1)
		endif (NOT DISABLE_${_setname})
	endif (DO_SSE_AUTODETECT)
endmacro(DEFINE_SSE_VAR)

# SSE optimizations:
DEFINE_SSE_VAR(SSE2)
DEFINE_SSE_VAR(SSE3)
DEFINE_SSE_VAR(SSE4_1)
DEFINE_SSE_VAR(SSE4_2)
DEFINE_SSE_VAR(SSE4_A)

include(CheckCXXCompilerFlag)

macro(CHECK_AND_ADD_COMPILE_OPTION _option _option_name)
  if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
    check_cxx_compiler_flag("-Werror=unused-command-line-argument ${_option}" ${_option_name}_AVAILABLE)
  else()
    check_cxx_compiler_flag(${_option} ${_option_name}_AVAILABLE)
  endif()
  if(${_option_name}_AVAILABLE)
    add_compile_options(${_option})
  endif()
endmacro()

# Add build flags for clang AND GCC
if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" OR CMAKE_COMPILER_IS_GNUCXX)
  # SSE2?
  if (CMAKE_G2O_HAS_SSE2)
    CHECK_AND_ADD_COMPILE_OPTION(-msse2 SSE2)
  endif()
  # SSE3?
  if (CMAKE_G2O_HAS_SSE3)
    CHECK_AND_ADD_COMPILE_OPTION(-msse3 SSE3)
    CHECK_AND_ADD_COMPILE_OPTION(-mssse3 SSSE3)
  endif()
  # SSE4*?
  if (CMAKE_G2O_HAS_SSE4_1)
    CHECK_AND_ADD_COMPILE_OPTION(-msse4.1 SSE41)
  endif()
  if (CMAKE_G2O_HAS_SSE4_2)
    CHECK_AND_ADD_COMPILE_OPTION(-msse4.2 SSE42)
  endif()
  if (CMAKE_G2O_HAS_SSE4_A)
    CHECK_AND_ADD_COMPILE_OPTION(-msse4a SSE4a)
  endif()
endif()
# End of of SSE* autodetect code -------

# Compiler specific options for gcc
if(CMAKE_COMPILER_IS_GNUCXX)
  message(STATUS "Compiling with GCC")

  # activate warnings !!!
  set(g2o_C_FLAGS "${g2o_C_FLAGS} -Wall -Wextra -Wpedantic")
  set(g2o_CXX_FLAGS "${g2o_CXX_FLAGS} -Wall -Wextra -Wpedantic")
endif(CMAKE_COMPILER_IS_GNUCXX)

if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  message(STATUS "Compiling with Clang")

  # activate all warnings
  set(g2o_C_FLAGS "${g2o_C_FLAGS} -Wall -Wextra -pedantic")
  set(g2o_CXX_FLAGS "${g2o_CXX_FLAGS} -Wall -Wextra -pedantic")
endif()

if(MSVC)
  message(STATUS "Compiling with MSVC")

  if (CMAKE_GENERATOR MATCHES "ARM(64)?$")
    set(MSVC_ARM ON)
  endif()

  add_definitions(-DNOMINMAX)
  add_definitions(-D_USE_MATH_DEFINES)
  add_definitions(-D_CRT_NONSTDC_NO_WARNINGS)

  # exception handling
  add_definitions("/EHsc")

  # SSE2 optimizations
  # No need to specify if building for x64 (actually, it generates an annoying warning)
  if (NOT MSVC_ARM)
    if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
        add_definitions("/arch:SSE2")
    endif()
  endif()

  if (BUILD_SHARED_LIBS)
    # disable warning on missing DLL interfaces
    add_definitions("/wd4251")
  endif()

  add_definitions("/bigobj") # Avoid C1128
  # Fix other stupid warnings:
  add_definitions(-D_CRT_SECURE_NO_WARNINGS=1)  # Avoid deprecated fprintf(), etc.
  add_definitions("/nologo")
  # TODO not sure this should be a thing
  add_definitions("/wd4244") # Conversion from double -> int
  add_definitions("/wd4267") # Conversion during return
  add_definitions("/wd4522") # Duplicated operator=() in Eigen headers

  # Bessel  include(CheckIfUnderscorePrefixedBesselFunctionsExist)
  include(CheckIfUnderscorePrefixedBesselFunctionsExist)
  check_if_underscore_prefixed_bessel_functions_exist(HAVE_UNDERSCORE_PREFIXED_BESSEL_FUNCTIONS)
  if (HAVE_UNDERSCORE_PREFIXED_BESSEL_FUNCTIONS)
    add_definitions(-DCERES_MSVC_USE_UNDERSCORE_PREFIXED_BESSEL_FUNCTIONS)
  endif()

endif(MSVC)

# specifying compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${g2o_CXX_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${g2o_C_FLAGS}")

find_package(Eigen3 3.3 REQUIRED)
find_package(nlohmann_json 3.3.0)

# Generate config.h
set(G2O_OPENGL_FOUND ${OPENGL_FOUND})
set(G2O_HAVE_CHOLMOD ${CHOLMOD_FOUND})
set(G2O_HAVE_CSPARSE ${G2O_USE_CSPARSE})
if (TARGET nlohmann_json::nlohmann_json)
  set(G2O_HAVE_JSON 1)
else()
  set(G2O_HAVE_JSON 0)
endif()
set(G2O_SHARED_LIBS ${BUILD_SHARED_LIBS})
set(G2O_LGPL_SHARED_LIBS ${BUILD_LGPL_SHARED_LIBS})

# Generate cmake configuration scripts
set(G2O_GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
set(G2O_VERSION_CONFIG "${G2O_GENERATED_DIR}/${PROJECT_NAME}ConfigVersion.cmake")
set(G2O_PROJECT_CONFIG "${G2O_GENERATED_DIR}/${PROJECT_NAME}Config.cmake")
set(G2O_TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets")
set(G2O_CONFIG_INSTALL_DIR "lib/cmake/${PROJECT_NAME}")
set(G2O_NAMESPACE "${PROJECT_NAME}::")
set(G2O_SRC_DIR "${PROJECT_SOURCE_DIR}")

include(CMakePackageConfigHelpers)
WRITE_BASIC_PACKAGE_VERSION_FILE(
    "${G2O_VERSION_CONFIG}" VERSION ${G2O_VERSION} COMPATIBILITY SameMajorVersion
)

configure_file(config.h.in "${PROJECT_BINARY_DIR}/g2o/config.h")
install(FILES ${PROJECT_BINARY_DIR}/g2o/config.h DESTINATION ${INCLUDES_DESTINATION}/g2o)

configure_file("${g2o_SOURCE_DIR}/cmake_modules/Config.cmake.in" "${G2O_PROJECT_CONFIG}" @ONLY)

install(
    FILES "${G2O_PROJECT_CONFIG}" "${G2O_VERSION_CONFIG}"
    DESTINATION "${G2O_CONFIG_INSTALL_DIR}")

install(
    EXPORT "${G2O_TARGETS_EXPORT_NAME}"
    NAMESPACE "${G2O_NAMESPACE}"
    DESTINATION "${G2O_CONFIG_INSTALL_DIR}")

option(G2O_BUILD_PACKAGE "Include package build config for cpack" OFF)
if(G2O_BUILD_PACKAGE)
  include(Packaging)
endif()

# Include the subdirectories
add_subdirectory(g2o)

# building unit test framework and our tests
option(BUILD_UNITTESTS "build unit test framework and the tests" OFF)
if(BUILD_UNITTESTS)
  enable_testing()
  add_subdirectory(unit_test)
endif()

# Benchmarks
option(G2O_BUILD_BENCHMARKS "build benchmarks" OFF)
if(G2O_BUILD_BENCHMARKS)
  find_package(benchmark)
  if(${benchmark_FOUND})
    add_subdirectory(benchmarks)
  else()
    message(WARNING "G2O_BUILD_BENCHMARKS was set to true, but the benchmark library cannot be found")
  endif()
endif()

# Python wrapper
set(G2O_BUILD_PYTHON OFF CACHE BOOL "Build python wrapper")
if(G2O_BUILD_PYTHON)
  message(STATUS "Compiling g2o's python wrapper")
  add_subdirectory(python)
endif(G2O_BUILD_PYTHON)
