# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cmake_minimum_required(VERSION 3.5)

if (NOT DEFINED PACKAGE_VERSION)
  set(PACKAGE_VERSION "1.0.0")
endif()

project("wangle" VERSION ${PACKAGE_VERSION} LANGUAGES CXX C)

# Set for FetchContent to skip find_package(wangle)
set(wangle_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}"
  CACHE INTERNAL "wangle source directory")

if (NOT DEFINED CPACK_GENERATOR)
  set(CPACK_GENERATOR "RPM")
endif()
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
include(CPack)

if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 20)
  set(CMAKE_CXX_STANDARD_REQUIRED ON)
  set(CMAKE_CXX_EXTENSIONS OFF)
  message(STATUS "setting C++ standard to C++${CMAKE_CXX_STANDARD}")
endif()

set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_MODULE_PATH
  "${CMAKE_CURRENT_SOURCE_DIR}/cmake/"
  # for in-fbsource builds
  "${CMAKE_CURRENT_SOURCE_DIR}/../opensource/fbcode_builder/CMake"
  # For shipit-transformed builds
  "${CMAKE_CURRENT_SOURCE_DIR}/../build/fbcode_builder/CMake"
  ${CMAKE_MODULE_PATH})

include(FBBuildOptions)
fb_activate_static_library_option()

# When installing Folly & Wangle in a non-default prefix, this will let
# projects linking against libwangle.so to find libfolly.so automatically.
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(INCLUDE_INSTALL_DIR include CACHE STRING
    "The subdirectory where header files should be installed")
set(LIB_INSTALL_DIR lib CACHE STRING
    "The subdirectory where libraries should be installed")
set(CMAKE_INSTALL_DIR lib/cmake/wangle CACHE STRING
    "The subdirectory where CMake package config files should be installed")

# Try finding folly via its installed CMake configuration file.
# This will also automatically find several libraries that we depend on through
# folly.
# If folly/fizz are being built from source (FetchContent), skip find_package
if (NOT DEFINED folly_SOURCE_DIR)
  find_package(folly CONFIG REQUIRED)
endif()

if (NOT DEFINED fizz_SOURCE_DIR)
  find_package(fizz CONFIG REQUIRED)
endif()
find_package(fmt CONFIG REQUIRED)
find_package(OpenSSL REQUIRED)

# Logging backend selection. Default is glog (preserves historical behavior);
# downstream builds can opt in to folly::xlog or fully disable logging.
# Interpolated into wangle-config.h.in as
# `#define WANGLE_LOGGING_@WANGLE_LOGGING_BACKEND@ 1` and consumed from
# wangle/util/Logging.h.
set(WANGLE_LOGGING_BACKEND "GLOG" CACHE STRING
  "Logging backend. Valid choices: GLOG, XLOG, DISABLED")
set_property(CACHE WANGLE_LOGGING_BACKEND PROPERTY STRINGS GLOG XLOG DISABLED)
if(NOT WANGLE_LOGGING_BACKEND MATCHES "^(GLOG|XLOG|DISABLED)$")
  message(FATAL_ERROR
    "WANGLE_LOGGING_BACKEND must be one of: GLOG, XLOG, DISABLED "
    "(got '${WANGLE_LOGGING_BACKEND}')")
endif()

# Glog is only required in the default GLOG backend; XLOG routes through
# folly::xlog and DISABLED strips logging entirely.
if(WANGLE_LOGGING_BACKEND STREQUAL "GLOG")
  find_package(Glog REQUIRED)
  add_compile_definitions(GLOG_USE_GLOG_EXPORT)
endif()
find_package(gflags CONFIG QUIET)
if (gflags_FOUND)
  message(STATUS "Found gflags from package config")
  message(STATUS "gflags_CONFIG=${gflags_CONFIG}")
else()
  find_package(Gflags REQUIRED)
endif()
find_package(LibEvent MODULE REQUIRED)
find_package(Threads REQUIRED)
if (UNIX AND NOT APPLE)
  find_package(Librt)
endif()

include(CheckAtomic)

# =============================================================================
# Granular CMake targets
# =============================================================================

# Include wangle functions for granular library targets
include(cmake/WangleFunctions.cmake)

# Set base directory for wangle includes
set(WANGLE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(WANGLE_BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..)

# Check for SSL_SESSION_dup for granular targets
include(CheckLibraryExists)
check_library_exists(
  ssl SSL_SESSION_dup "${OPENSSL_SSL_LIBRARY}" WANGLE_HAVE_SSL_SESSION_DUP
)

# Generate <wangle/wangle-config.h> for this build. The template interpolates
# WANGLE_LOGGING_BACKEND into a `#define WANGLE_LOGGING_<BACKEND> 1`, which
# wangle/util/Logging.h consumes to pick the macro implementations.
configure_file(
  wangle-config.h.in
  ${CMAKE_CURRENT_BINARY_DIR}/generated/wangle/wangle-config.h)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/generated/wangle/wangle-config.h
  DESTINATION ${INCLUDE_INSTALL_DIR}/wangle/)

# Header-only INTERFACE target carrying the generated <wangle/wangle-config.h>
# include dir. wangle/util/CMakeLists.txt lists this as an EXPORTED_DEP of
# wangle_util_logging so consumers transitively pick up the include path.
# Must be defined before add_subdirectory(util) since util/ references it.
add_library(wangle_config INTERFACE)
target_include_directories(wangle_config INTERFACE
  $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/generated>
  $<INSTALL_INTERFACE:${INCLUDE_INSTALL_DIR}>)
install(TARGETS wangle_config EXPORT wangle-exports)

# Add subdirectories with granular targets (order matters for dependencies)
add_subdirectory(util)

# The generated util/CMakeLists.txt lists both Folly::folly_glog and
# Folly::folly_logging_logging as unconditional INTERFACE deps. Narrow that to
# match the selected backend so downstream code doesn't pull in libraries it
# isn't actually using.
if(WANGLE_LOGGING_BACKEND STREQUAL "XLOG")
  set_target_properties(wangle_util_logging PROPERTIES
    INTERFACE_LINK_LIBRARIES "wangle_config;Folly::folly_logging_logging")
elseif(WANGLE_LOGGING_BACKEND STREQUAL "DISABLED")
  set_target_properties(wangle_util_logging PROPERTIES
    INTERFACE_LINK_LIBRARIES "wangle_config")
endif()

add_subdirectory(ssl)
add_subdirectory(acceptor)
add_subdirectory(channel)
add_subdirectory(codec)
add_subdirectory(bootstrap)
add_subdirectory(service)
add_subdirectory(client/persistence)
add_subdirectory(client/ssl)

# Create the monolithic wangle library for backward compatibility
wangle_create_monolithic_library()

# Resolve deferred dependencies between wangle targets
wangle_resolve_deferred_dependencies()

# Add SSL_SESSION_dup define to monolithic library
if(WANGLE_HAVE_SSL_SESSION_DUP)
  target_compile_definitions(wangle PRIVATE WANGLE_HAVE_SSL_SESSION_DUP)
endif()

# Install the monolithic wangle library
install(
  TARGETS wangle
  EXPORT wangle-exports
  LIBRARY DESTINATION ${LIB_INSTALL_DIR}
  ARCHIVE DESTINATION ${LIB_INSTALL_DIR}
)

# =============================================================================
# Header installation
# =============================================================================

# Collect all headers via recursive glob (like folly/fizz does)
file(GLOB_RECURSE WANGLE_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
    "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
# Filter out test, facebook, and other non-public directories
list(FILTER WANGLE_HEADERS EXCLUDE REGEX "(^|/)test/")
list(FILTER WANGLE_HEADERS EXCLUDE REGEX "(^|/)facebook/")
list(FILTER WANGLE_HEADERS EXCLUDE REGEX "(^|/)build/")
list(FILTER WANGLE_HEADERS EXCLUDE REGEX "(^|/)example/")

# Collect test headers separately
file(GLOB_RECURSE WANGLE_TEST_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
    "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
list(FILTER WANGLE_TEST_HEADERS INCLUDE REGEX "(^|/)test/")
list(FILTER WANGLE_TEST_HEADERS EXCLUDE REGEX "(^|/)facebook/")

# Install headers preserving directory structure
wangle_install_headers(wangle . ${WANGLE_HEADERS})

# =============================================================================
# CMake package configuration
# =============================================================================

include(CMakePackageConfigHelpers)
configure_package_config_file(
  cmake/wangle-config.cmake.in
  wangle-config.cmake
  INSTALL_DESTINATION ${CMAKE_INSTALL_DIR}
  PATH_VARS
    INCLUDE_INSTALL_DIR
    CMAKE_INSTALL_DIR
)
install(
  FILES ${CMAKE_CURRENT_BINARY_DIR}/wangle-config.cmake
  DESTINATION ${CMAKE_INSTALL_DIR}
)
install(EXPORT wangle-exports
        FILE wangle-targets.cmake
        NAMESPACE wangle::
        DESTINATION ${CMAKE_INSTALL_DIR})

# =============================================================================
# Tests
# =============================================================================

IF(CMAKE_CROSSCOMPILING)
   option(BUILD_TESTS "BUILD_TESTS" OFF)
ELSE(CMAKE_CROSSCOMPILING)
   option(BUILD_TESTS "BUILD_TESTS" ON)
ENDIF(CMAKE_CROSSCOMPILING)

if(BUILD_TESTS)
  enable_testing()

  find_package(GMock MODULE)
  if(NOT LIBGMOCK_FOUND)
    include(ExternalProject)

    # Download and install GoogleMock
    ExternalProject_Add(
        gtest
        GIT_REPOSITORY https://github.com/google/googletest.git
        GIT_TAG release-1.8.0
        PREFIX gtest
        # Disable install step
        INSTALL_COMMAND ""
        LOG_DOWNLOAD ON
        LOG_UPDATE 1
        LOG_CONFIGURE ON
        LOG_BUILD ON
        LOG_TEST 1
        LOG_INSTALL 1
    )

    # Create a libgmock target to be used as a dependency by test programs
    add_library(libgmock IMPORTED STATIC GLOBAL)
    add_dependencies(libgmock gtest)
    add_library(libgmock_main IMPORTED STATIC GLOBAL)
    add_dependencies(libgmock_main gtest)

    # Set gmock properties
    ExternalProject_Get_Property(gtest source_dir binary_dir)
    set_target_properties(libgmock PROPERTIES
        "IMPORTED_LOCATION" "${binary_dir}/googlemock/libgmock.a"
        "IMPORTED_LINK_INTERFACE_LIBRARIES" "${CMAKE_THREAD_LIBS_INIT}"
    )
    set_target_properties(libgmock_main PROPERTIES
        "IMPORTED_LOCATION" "${binary_dir}/googlemock/libgmock_main.a"
        "IMPORTED_LINK_INTERFACE_LIBRARIES" "${CMAKE_THREAD_LIBS_INIT}"
    )
    set(LIBGMOCK_LIBRARIES libgmock libgmock_main)
    set(LIBGMOCK_INCLUDE_DIR "${source_dir}/googlemock/include")
    set(LIBGTEST_INCLUDE_DIR "${source_dir}/googletest/include")
  endif()

  macro(add_gtest test_source test_name)
  add_executable(${test_name} ${test_source} test/TestMain.cpp)
  target_include_directories(
    ${test_name} PUBLIC ${LIBGMOCK_INCLUDE_DIR} ${LIBGTEST_INCLUDE_DIR}
  )
  target_link_libraries(
    ${test_name}
    Folly::folly_init_init
    Folly::folly_testing_test_util
    wangle
    ${LIBGMOCK_LIBRARIES}
    ${LIBEVENT_LIB}
  )
  add_test(${test_name} bin/${test_name})
  endmacro(add_gtest)

  # this test segfaults
  add_gtest(acceptor/test/ConnectionManagerTest.cpp ConnectionManagerTest)
  add_gtest(
    acceptor/test/LoadShedConfigurationTest.cpp LoadShedConfigurationTest
  )
  add_gtest(
    acceptor/test/PeekingAcceptorHandshakeHelperTest.cpp
    PeekingAcceptorHandshakeHelperTest
  )
  add_gtest(bootstrap/test/BootstrapTest.cpp BootstrapTest)
  add_gtest(
    channel/broadcast/test/BroadcastHandlerTest.cpp BroadcastHandlerTest
  )
  add_gtest(channel/broadcast/test/BroadcastPoolTest.cpp BroadcastPoolTest)
  add_gtest(
    channel/broadcast/test/ObservingHandlerTest.cpp ObservingHandlerTest
  )
  add_gtest(channel/test/AsyncSocketHandlerTest.cpp AsyncSocketHandlerTest)
  add_gtest(
    channel/test/OutputBufferingHandlerTest.cpp OutputBufferingHandlerTest
  )
  add_gtest(channel/test/PipelineTest.cpp PipelineTest)
  add_gtest(codec/test/CodecTest.cpp CodecTest)
  # this test fails with an exception
  #  add_gtest(service/test/ServiceTest.cpp ServiceTest)
  # this test requires arguments?
  #  add_gtest(ssl/test/SSLCacheTest.cpp SSLCacheTest)
  add_gtest(ssl/test/SSLContextManagerTest.cpp SSLContextManagerTest)
  add_gtest(ssl/test/TLSCredProcessorTest.cpp TLSCredProcessorTest)
  add_gtest(
    ssl/test/TLSInMemoryTicketProcessorTest.cpp TLSInMemoryTicketProcessorTest
  )
  add_gtest(util/test/FilePollerTest.cpp FilePollerTest)

  # Install test headers using recursive glob
  wangle_install_headers(
    wangle ${CMAKE_CURRENT_SOURCE_DIR} ${WANGLE_TEST_HEADERS}
  )
endif()

# =============================================================================
# Examples
# =============================================================================

option(BUILD_EXAMPLES "BUILD_EXAMPLES" OFF)

if(BUILD_EXAMPLES)
  add_executable(EchoClient example/echo/EchoClient.cpp)
  target_link_libraries(EchoClient wangle)
  add_executable(EchoServer example/echo/EchoServer.cpp)
  target_link_libraries(EchoServer wangle)
  add_executable(TelnetClient example/telnet/TelnetClient.cpp)
  target_link_libraries(TelnetClient wangle)
  add_executable(TelnetServer example/telnet/TelnetServer.cpp)
  target_link_libraries(TelnetServer wangle)
  add_executable(ProxyServer example/proxy/Proxy.cpp)
  target_link_libraries(ProxyServer wangle)
  add_executable(
    AcceptServer example/accept_steering/accept_steering_server.cpp
  )
  target_link_libraries(AcceptServer wangle)
  add_executable(BroadcastProxy example/broadcast/BroadcastProxy.cpp)
  target_link_libraries(BroadcastProxy wangle)
endif()
