cmake_minimum_required(VERSION 3.16.3) # version on Ubuntu Focal

project(behaviortree_cpp VERSION 4.9.0 LANGUAGES C CXX)

# create compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 17)

#---- project configuration ----
option(BTCPP_SHARED_LIBS "Build shared libraries" ON)
option(BTCPP_BUILD_TOOLS "Build commandline tools" ON)
option(BTCPP_EXAMPLES   "Build tutorials and examples" ON)
option(BUILD_TESTING "Build the unit tests" ON)
option(BTCPP_GROOT_INTERFACE "Add Groot2 connection. Requires ZeroMQ" ON)
option(BTCPP_SQLITE_LOGGING "Add SQLite logging." ON)
option(BTCPP_ENABLE_ASAN "Enable Address Sanitizer" OFF)
option(BTCPP_ENABLE_UBSAN "Enable Undefined Behavior Sanitizer" OFF)
option(BTCPP_ENABLE_TSAN "Enable Thread Sanitizer" OFF)

option(USE_V3_COMPATIBLE_NAMES  "Use some alias to compile more easily old 3.x code" OFF)
option(ENABLE_FUZZING "Enable fuzzing builds" OFF)
option(USE_AFLPLUSPLUS "Use AFL++ instead of libFuzzer" OFF)
option(ENABLE_DEBUG "Enable debug build with full symbols" OFF)
option(FORCE_STATIC_LINKING "Force static linking of all dependencies" OFF)

option(USE_VENDORED_CPPZMQ "Use the bundled version of cppzmq" ON)
option(USE_VENDORED_FLATBUFFERS "Use the bundled version of flatbuffers" ON)
option(USE_VENDORED_MINICORO "Use the bundled version of minicoro" ON)
option(USE_VENDORED_MINITRACE "Use the bundled version of minitrace" ON)

set(BTCPP_LIB_DESTINATION     lib)
set(BTCPP_INCLUDE_DESTINATION include)
set(BTCPP_BIN_DESTINATION     bin)

set(BASE_FLAGS "")

if(ENABLE_DEBUG)
    list(APPEND BASE_FLAGS
        -g3
        -ggdb3
        -O0
        -fno-omit-frame-pointer
    )
endif()

# Include fuzzing configuration if enabled
if(ENABLE_FUZZING)
    include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/fuzzing_build.cmake)
else()
    # Apply base flags for non-fuzzing builds
    add_compile_options(${BASE_FLAGS})
    add_link_options(${BASE_FLAGS})
endif()

set(CMAKE_CONFIG_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CONFIG_PATH}")

include(sanitizers)

set(BTCPP_LIBRARY ${PROJECT_NAME})

if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
    message(STATUS "Setting build type to 'Release' as none was specified.")
    set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
        "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()

if(MSVC)
    add_definitions(-D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN)
else()
    add_definitions(-Wpedantic -fno-omit-frame-pointer)
endif()

if(USE_V3_COMPATIBLE_NAMES)
    add_definitions(-DUSE_BTCPP3_OLD_NAMES)
endif()

# Update the policy setting to avoid an error when loading the ament_cmake package
# at the current cmake version level
if(POLICY CMP0057)
    cmake_policy(SET CMP0057 NEW)
endif()

find_package(ament_cmake QUIET)

if ( ament_cmake_FOUND )

    add_definitions( -DUSING_ROS2 )
    message(STATUS "------------------------------------------")
    message(STATUS "BehaviorTree is being built using AMENT.")
    message(STATUS "------------------------------------------")
    include(cmake/ament_build.cmake)

    find_package(tinyxml2_vendor REQUIRED)
    find_package(TinyXML2 REQUIRED)
else()
    message(STATUS "------------------------------------------")
    message(STATUS "BehaviorTree is being built without AMENT.")
    message(STATUS "------------------------------------------")
    include(cmake/conan_build.cmake)

    option(USE_VENDORED_TINYXML2 "Use the bundled version of tinyxml2" ON)
    if(USE_VENDORED_TINYXML2)
        add_subdirectory(3rdparty/tinyxml2)
    else()
        find_package(tinyxml2 REQUIRED)
    endif()
endif()

#############################################################
# Handle dependencies

find_package(Threads REQUIRED)

if(BTCPP_GROOT_INTERFACE)
    if(USE_VENDORED_CPPZMQ)
        add_subdirectory(3rdparty/cppzmq)
    else()
        find_package(cppzmq REQUIRED)
    endif()
endif()

if(BTCPP_SQLITE_LOGGING)
    find_package(SQLite3 REQUIRED)
endif()

if(USE_VENDORED_FLATBUFFERS)
    add_subdirectory(3rdparty/flatbuffers)
else()
    find_package(flatbuffers REQUIRED)
endif()

if(USE_VENDORED_MINICORO)
    add_subdirectory(3rdparty/minicoro)
else()
    find_package(minicoro REQUIRED)
endif()

if(USE_VENDORED_MINITRACE)
    add_subdirectory(3rdparty/minitrace)
else()
    find_package(minitrace REQUIRED)
endif()

list(APPEND BT_SOURCE
    src/action_node.cpp
    src/basic_types.cpp
    src/behavior_tree.cpp
    src/blackboard.cpp
    src/bt_factory.cpp
    src/decorator_node.cpp
    src/condition_node.cpp
    src/control_node.cpp
    src/shared_library.cpp
    src/tree_node.cpp
    src/script_parser.cpp
    src/script_tokenizer.cpp
    src/json_export.cpp
    src/xml_parsing.cpp

    src/actions/test_node.cpp
    src/actions/sleep_node.cpp
    src/actions/updated_action.cpp

    src/decorators/delay_node.cpp
    src/decorators/inverter_node.cpp
    src/decorators/repeat_node.cpp
    src/decorators/retry_node.cpp
    src/decorators/subtree_node.cpp
    src/decorators/timeout_node.cpp
    src/decorators/updated_decorator.cpp

    src/controls/if_then_else_node.cpp
    src/controls/fallback_node.cpp
    src/controls/parallel_node.cpp
    src/controls/parallel_all_node.cpp
    src/controls/reactive_sequence.cpp
    src/controls/reactive_fallback.cpp
    src/controls/sequence_node.cpp
    src/controls/sequence_with_memory_node.cpp
    src/controls/switch_node.cpp
    src/controls/try_catch_node.cpp
    src/controls/while_do_else_node.cpp

    src/loggers/bt_cout_logger.cpp
    src/loggers/bt_file_logger_v2.cpp
    src/loggers/bt_minitrace_logger.cpp
    src/loggers/bt_observer.cpp
    )


if(BTCPP_GROOT_INTERFACE)
    # should be found already, at this stage
    list(APPEND BT_SOURCE  src/loggers/groot2_publisher.cpp )
endif()

if(BTCPP_SQLITE_LOGGING)
    list(APPEND BT_SOURCE  src/loggers/bt_sqlite_logger.cpp )
endif()

######################################################

if (UNIX)
    list(APPEND BT_SOURCE src/shared_library_UNIX.cpp )
endif()

if (WIN32)
    set(CMAKE_DEBUG_POSTFIX "d")
    list(APPEND BT_SOURCE src/shared_library_WIN.cpp )
endif()

if (BTCPP_SHARED_LIBS)
    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
    add_library(${BTCPP_LIBRARY} SHARED ${BT_SOURCE})
else()
    set(CMAKE_POSITION_INDEPENDENT_CODE ON)
    add_library(${BTCPP_LIBRARY} STATIC ${BT_SOURCE})
endif()

message(STATUS "BTCPP_EXTRA_LIBRARIES: ${BTCPP_EXTRA_LIBRARIES}")

target_link_libraries(${BTCPP_LIBRARY}
    PRIVATE
        Threads::Threads
        ${CMAKE_DL_LIBS}
        $<BUILD_INTERFACE:minitrace::minitrace>
        $<BUILD_INTERFACE:tinyxml2::tinyxml2>
        $<BUILD_INTERFACE:minicoro::minicoro>
        $<BUILD_INTERFACE:flatbuffers::flatbuffers>
    PUBLIC
        ${BTCPP_EXTRA_LIBRARIES}
)

if(BTCPP_GROOT_INTERFACE)
    target_link_libraries(${BTCPP_LIBRARY}
        PUBLIC
            $<BUILD_INTERFACE:cppzmq>
    )
endif()

if(BTCPP_SQLITE_LOGGING)
    target_link_libraries(${BTCPP_LIBRARY}
        PRIVATE
            $<BUILD_INTERFACE:SQLite::SQLite3>
    )
endif()

target_include_directories(${BTCPP_LIBRARY}
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:include>
    PRIVATE
        ${BTCPP_EXTRA_INCLUDE_DIRS}
    )

target_compile_definitions(${BTCPP_LIBRARY} PUBLIC BTCPP_LIBRARY_VERSION="${CMAKE_PROJECT_VERSION}")

target_compile_features(${BTCPP_LIBRARY} PUBLIC cxx_std_17)

if(MSVC)
    target_compile_options(${BTCPP_LIBRARY} PRIVATE "/source-charset:utf-8")
else()
    if(ENABLE_DEBUG)
        target_compile_options(${BTCPP_LIBRARY} PRIVATE -Wall -Wextra -g3 -ggdb3 -O0 -fno-omit-frame-pointer)
    else()
        target_compile_options(${BTCPP_LIBRARY} PRIVATE -Wall -Wextra)
    endif()
endif()

add_library(BT::${BTCPP_LIBRARY} ALIAS ${BTCPP_LIBRARY})

# Add fuzzing targets
if(ENABLE_FUZZING)
    add_fuzzing_targets()
endif()

#############################################################
message( STATUS "BTCPP_LIB_DESTINATION:   ${BTCPP_LIB_DESTINATION} " )
message( STATUS "BTCPP_INCLUDE_DESTINATION: ${BTCPP_INCLUDE_DESTINATION} " )

if (BUILD_TESTING OR BTCPP_EXAMPLES)
    add_subdirectory(sample_nodes)
endif()

######################################################

include(CTest)
message( STATUS "BUILD_TESTING:   ${BUILD_TESTING} " )
if (BUILD_TESTING)
    add_subdirectory(tests)
endif()

if(BTCPP_BUILD_TOOLS)
    add_subdirectory(tools)
endif()

if(BTCPP_EXAMPLES)
    add_subdirectory(examples)
endif()

######################################################
# Generate .clangd configuration file for standalone header checking
file(WRITE ${PROJECT_SOURCE_DIR}/.clangd
"CompileFlags:
  Add:
    - -xc++
    - -std=c++17
    - -I${PROJECT_SOURCE_DIR}/include
    - -I${PROJECT_SOURCE_DIR}/3rdparty
    - -I${PROJECT_SOURCE_DIR}/3rdparty/minitrace
    - -I${PROJECT_SOURCE_DIR}/3rdparty/tinyxml2
    - -I${PROJECT_SOURCE_DIR}/3rdparty/minicoro
")

######################################################
# INSTALL

INSTALL(TARGETS ${BTCPP_LIBRARY}
    EXPORT ${BTCPP_LIBRARY}Targets
    ARCHIVE DESTINATION ${BTCPP_LIB_DESTINATION}
    LIBRARY DESTINATION ${BTCPP_LIB_DESTINATION}
    RUNTIME DESTINATION ${BTCPP_BIN_DESTINATION}
    INCLUDES DESTINATION ${BTCPP_INCLUDE_DESTINATION}
    )

INSTALL( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
    DESTINATION ${BTCPP_INCLUDE_DESTINATION}
    FILES_MATCHING PATTERN "*.h*")

export_btcpp_package()
