# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you 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.

add_custom_target(arrow_flight)

arrow_install_all_headers("arrow/flight")

# If libarrow_flight.a is only built, "pkg-config --cflags --libs
# arrow-flight" outputs build flags for static linking not shared
# linking. ARROW_FLIGHT_PC_* except ARROW_FLIGHT_PC_*_PRIVATE are for
# the static linking case.
if(NOT ARROW_BUILD_SHARED AND ARROW_BUILD_STATIC)
  string(APPEND ARROW_FLIGHT_PC_CFLAGS "${ARROW_FLIGHT_PC_CFLAGS_PRIVATE}")
  set(ARROW_FLIGHT_PC_CFLAGS_PRIVATE "")
endif()

set(ARROW_FLIGHT_LINK_LIBS gRPC::grpc++ ${ARROW_PROTOBUF_LIBPROTOBUF})
if(ARROW_WITH_OPENTELEMETRY)
  list(APPEND ARROW_FLIGHT_LINK_LIBS ${ARROW_OPENTELEMETRY_LIBS})
endif()
if(WIN32)
  list(APPEND ARROW_FLIGHT_LINK_LIBS ws2_32.lib)
endif()
# Updating the MACOSX_DEPLOYMENT_TARGET to 12 required us to explicitly
# link Flight with OpenSSL on macOS. Read this comment for more details:
# https://github.com/apache/arrow/pull/43137#pullrequestreview-2267476893
if(APPLE AND ARROW_USE_OPENSSL)
  list(APPEND ARROW_FLIGHT_LINK_LIBS ${ARROW_OPENSSL_LIBS})
endif()

set(ARROW_FLIGHT_STATIC_INSTALL_INTERFACE_LIBS Arrow::arrow_static)
if(ARROW_PROTOBUF_ARROW_CMAKE_PACKAGE_NAME STREQUAL "ArrowFlight")
  if(Protobuf_SOURCE STREQUAL "SYSTEM")
    list(APPEND ARROW_FLIGHT_STATIC_INSTALL_INTERFACE_LIBS ${ARROW_PROTOBUF_LIBPROTOBUF})
  endif()
endif()

set(ARROW_FLIGHT_TEST_LINKAGE "${ARROW_TEST_LINKAGE}")
if(Protobuf_USE_STATIC_LIBS)
  message(STATUS "Linking Arrow Flight tests statically due to static Protobuf")
  set(ARROW_FLIGHT_TEST_LINKAGE "static")
endif()
if(NOT ARROW_GRPC_USE_SHARED)
  message(STATUS "Linking Arrow Flight tests statically due to static gRPC")
  set(ARROW_FLIGHT_TEST_LINKAGE "static")
endif()

set(ARROW_FLIGHT_TEST_INTERFACE_LIBS)
if(ARROW_BUILD_BENCHMARKS
   OR ARROW_BUILD_INTEGRATION
   OR ARROW_BUILD_TESTS)
  if(ARROW_FLIGHT_TEST_LINKAGE STREQUAL "static")
    if(NOT ARROW_BUILD_STATIC)
      message(STATUS "If static Protobuf or gRPC are used, Arrow must be built statically"
      )
      message(STATUS "(These libraries have global state, and linkage must be consistent)"
      )
      message(FATAL_ERROR "Must build Arrow statically to link Flight tests statically")
    endif()
    set(ARROW_FLIGHT_TEST_LINK_LIBS arrow_flight_static arrow_flight_testing_static)
    list(APPEND ARROW_FLIGHT_TEST_LINK_LIBS ${ARROW_TEST_STATIC_LINK_LIBS})
    if(ARROW_CUDA)
      list(APPEND ARROW_FLIGHT_TEST_INTERFACE_LIBS arrow_cuda_static)
      list(APPEND ARROW_FLIGHT_TEST_LINK_LIBS arrow_cuda_static)
    endif()
  else()
    set(ARROW_FLIGHT_TEST_LINK_LIBS arrow_flight_shared arrow_flight_testing_shared
                                    ${ARROW_TEST_SHARED_LINK_LIBS})
    if(ARROW_CUDA)
      list(APPEND ARROW_FLIGHT_TEST_INTERFACE_LIBS arrow_cuda_shared)
      list(APPEND ARROW_FLIGHT_TEST_LINK_LIBS arrow_cuda_shared)
    endif()
  endif()
endif()
list(APPEND ARROW_FLIGHT_TEST_LINK_LIBS gRPC::grpc++)

# TODO(wesm): Protobuf shared vs static linking

set(FLIGHT_PROTO_PATH "${ARROW_SOURCE_DIR}/../format")
set(FLIGHT_PROTO "${ARROW_SOURCE_DIR}/../format/Flight.proto")

set(FLIGHT_GENERATED_PROTO_FILES
    "${CMAKE_CURRENT_BINARY_DIR}/Flight.pb.cc" "${CMAKE_CURRENT_BINARY_DIR}/Flight.pb.h"
    "${CMAKE_CURRENT_BINARY_DIR}/Flight.grpc.pb.cc"
    "${CMAKE_CURRENT_BINARY_DIR}/Flight.grpc.pb.h")

set(PROTO_DEPENDS ${FLIGHT_PROTO} ${ARROW_PROTOBUF_LIBPROTOBUF} gRPC::grpc_cpp_plugin)

set(FLIGHT_PROTOC_COMMAND ${ARROW_PROTOBUF_PROTOC} "-I${FLIGHT_PROTO_PATH}")
if(Protobuf_VERSION VERSION_LESS 3.15)
  list(APPEND FLIGHT_PROTOC_COMMAND "--experimental_allow_proto3_optional")
endif()
add_custom_command(OUTPUT ${FLIGHT_GENERATED_PROTO_FILES}
                   DEPENDS ${PROTO_DEPENDS} ARGS
                   COMMAND ${FLIGHT_PROTOC_COMMAND}
                           "--cpp_out=${CMAKE_CURRENT_BINARY_DIR}" "${FLIGHT_PROTO}"
                   COMMAND ${FLIGHT_PROTOC_COMMAND}
                           "--grpc_out=${CMAKE_CURRENT_BINARY_DIR}"
                           "--plugin=protoc-gen-grpc=$<TARGET_FILE:gRPC::grpc_cpp_plugin>"
                           "${FLIGHT_PROTO}")

# Set common properties for C++ sources files generated by protoc
function(arrow_set_generated_proto_files_properties)
  set(GENERATED_FILES ${ARGN})
  set_source_files_properties(${GENERATED_FILES} PROPERTIES GENERATED TRUE)
  if(MSVC)
    # Suppress missing dll-interface warning
    set_source_files_properties(${GENERATED_FILES}
                                PROPERTIES COMPILE_OPTIONS "/wd4251"
                                           SKIP_UNITY_BUILD_INCLUSION TRUE)
  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    # Disable -Wmissing-declarations
    set_source_files_properties(${GENERATED_FILES} PROPERTIES COMPILE_OPTIONS
                                                              "-Wno-missing-declarations")
  endif()
endfunction()

arrow_set_generated_proto_files_properties(${FLIGHT_GENERATED_PROTO_FILES})

add_custom_target(flight_grpc_gen ALL DEPENDS ${FLIGHT_GENERATED_PROTO_FILES})

# <KLUDGE> -Werror / /WX cause try_compile to fail because there seems to be no
# way to pass -isystem $GRPC_INCLUDE_DIR instead of -I$GRPC_INCLUDE_DIR
set(CMAKE_CXX_FLAGS_BACKUP "${CMAKE_CXX_FLAGS}")
string(REPLACE "/WX" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
string(REPLACE "-Werror " " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")

if(ARROW_GRPC_VERSION VERSION_GREATER_EQUAL "1.43")
  add_definitions(-DGRPC_USE_TLS_CHANNEL_CREDENTIALS_OPTIONS
                  -DGRPC_NAMESPACE_FOR_TLS_CREDENTIALS_OPTIONS=grpc::experimental
                  -DGRPC_USE_CERTIFICATE_VERIFIER)
elseif(ARROW_GRPC_VERSION VERSION_GREATER_EQUAL "1.36")
  add_definitions(-DGRPC_USE_TLS_CHANNEL_CREDENTIALS_OPTIONS
                  -DGRPC_NAMESPACE_FOR_TLS_CREDENTIALS_OPTIONS=grpc::experimental)
elseif(ARROW_GRPC_VERSION VERSION_GREATER_EQUAL "1.34")
  add_definitions(-DGRPC_USE_TLS_CHANNEL_CREDENTIALS_OPTIONS
                  -DGRPC_USE_TLS_CHANNEL_CREDENTIALS_OPTIONS_ROOT_CERTS
                  -DGRPC_NAMESPACE_FOR_TLS_CREDENTIALS_OPTIONS=grpc::experimental)
elseif(ARROW_GRPC_VERSION VERSION_GREATER_EQUAL "1.32")
  add_definitions(-DGRPC_NAMESPACE_FOR_TLS_CREDENTIALS_OPTIONS=grpc::experimental)
else()
  add_definitions(-DGRPC_NAMESPACE_FOR_TLS_CREDENTIALS_OPTIONS=grpc_impl::experimental)
endif()

# Was in a different namespace, or simply not supported, prior to this
if(ARROW_GRPC_VERSION VERSION_GREATER_EQUAL "1.40")
  add_definitions(-DGRPC_ENABLE_ASYNC)
endif()

# </KLUDGE> Restore the CXXFLAGS that were modified above
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_BACKUP}")

# Note, we do not compile the generated gRPC sources directly, instead
# compiling them via protocol_internal.cc which contains some gRPC template
# overrides to enable Flight-specific optimizations. See comments in
# protocol_internal.cc
set(ARROW_FLIGHT_SRCS
    "${CMAKE_CURRENT_BINARY_DIR}/Flight.pb.cc"
    client.cc
    client_cookie_middleware.cc
    client_tracing_middleware.cc
    cookie_internal.cc
    middleware.cc
    serialization_internal.cc
    server.cc
    server_auth.cc
    server_tracing_middleware.cc
    transport.cc
    transport_server.cc
    # Bundle the gRPC impl with libarrow_flight
    transport/grpc/grpc_client.cc
    transport/grpc/grpc_server.cc
    transport/grpc/serialization_internal.cc
    transport/grpc/protocol_grpc_internal.cc
    transport/grpc/util_internal.cc
    types.cc)

# Handle Unity build header conflicts on Windows.
if(CMAKE_UNITY_BUILD AND WIN32)
  set_source_files_properties(client.cc
                              cookie_internal.cc
                              serialization_internal.cc
                              server.cc
                              transport/grpc/serialization_internal.cc
                              transport/grpc/protocol_grpc_internal.cc
                              transport/grpc/util_internal.cc
                              types.cc
                              PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE)
endif()

if(ARROW_WITH_OPENTELEMETRY)
  list(APPEND ARROW_FLIGHT_SRCS otel_logging.cc)
endif()

add_arrow_lib(arrow_flight
              CMAKE_PACKAGE_NAME
              ArrowFlight
              PKG_CONFIG_NAME
              arrow-flight
              OUTPUTS
              ARROW_FLIGHT_LIBRARIES
              SOURCES
              ${ARROW_FLIGHT_SRCS}
              DEPENDENCIES
              flight_grpc_gen
              SHARED_LINK_FLAGS
              ${ARROW_VERSION_SCRIPT_FLAGS} # Defined in cpp/arrow/CMakeLists.txt
              SHARED_LINK_LIBS
              # We must use gRPC::grpc++ first. If gRPC::grpc++
              # depends on bundled Abseil, bundled Abseil and system
              # Abseil may be mixed.
              #
              # See also a comment for "if(ARROW_GCS)" in
              # cpp/CMakeLists.txt.
              ${ARROW_FLIGHT_LINK_LIBS}
              arrow_shared
              SHARED_INSTALL_INTERFACE_LIBS
              Arrow::arrow_shared
              STATIC_LINK_LIBS
              ${ARROW_FLIGHT_LINK_LIBS}
              arrow_static
              STATIC_INSTALL_INTERFACE_LIBS
              ${ARROW_FLIGHT_STATIC_INSTALL_INTERFACE_LIBS})

if(ARROW_BUILD_STATIC AND WIN32)
  target_compile_definitions(arrow_flight_static PUBLIC ARROW_FLIGHT_STATIC)
endif()

foreach(LIB_TARGET ${ARROW_FLIGHT_LIBRARIES})
  target_compile_definitions(${LIB_TARGET} PRIVATE ARROW_FLIGHT_EXPORTING)
endforeach()

# Define arrow_flight_testing library
if(ARROW_TESTING)
  if(ARROW_BUILD_SHARED AND ARROW_BUILD_STATIC)
    set(ARROW_FLIGHT_TESTING_SHARED_LINK_LIBS arrow_flight_shared)
    set(ARROW_FLIGHT_TESTING_STATIC_LINK_LIBS arrow_flight_static)
    set(ARROW_FLIGHT_TESTING_SHARED_INSTALL_INTERFACE_LIBS
        ArrowFlight::arrow_flight_shared)
    set(ARROW_FLIGHT_TESTING_STATIC_INSTALL_INTERFACE_LIBS
        ArrowFlight::arrow_flight_static)
  elseif(ARROW_BUILD_SHARED)
    set(ARROW_FLIGHT_TESTING_SHARED_LINK_LIBS arrow_flight_shared)
    set(ARROW_FLIGHT_TESTING_STATIC_LINK_LIBS arrow_flight_shared)
    set(ARROW_FLIGHT_TESTING_SHARED_INSTALL_INTERFACE_LIBS
        ArrowFlight::arrow_flight_shared)
    set(ARROW_FLIGHT_TESTING_STATIC_INSTALL_INTERFACE_LIBS
        ArrowFlight::arrow_flight_shared)
  else()
    set(ARROW_FLIGHT_TESTING_SHARED_LINK_LIBS arrow_flight_static)
    set(ARROW_FLIGHT_TESTING_STATIC_LINK_LIBS arrow_flight_static)
    set(ARROW_FLIGHT_TESTING_SHARED_INSTALL_INTERFACE_LIBS
        ArrowFlight::arrow_flight_static)
    set(ARROW_FLIGHT_TESTING_STATIC_INSTALL_INTERFACE_LIBS
        ArrowFlight::arrow_flight_static)
  endif()
  if(ARROW_FLIGHT_TEST_LINKAGE STREQUAL "shared")
    list(APPEND ARROW_FLIGHT_TESTING_SHARED_LINK_LIBS arrow_testing_shared)
    list(APPEND ARROW_FLIGHT_TESTING_STATIC_LINK_LIBS arrow_testing_shared)
    list(APPEND ARROW_FLIGHT_TESTING_SHARED_INSTALL_INTERFACE_LIBS
         ArrowTesting::arrow_testing_shared)
    list(APPEND ARROW_FLIGHT_TESTING_STATIC_INSTALL_INTERFACE_LIBS
         ArrowTesting::arrow_testing_shared)
  else()
    list(APPEND ARROW_FLIGHT_TESTING_SHARED_LINK_LIBS arrow_testing_static)
    list(APPEND ARROW_FLIGHT_TESTING_STATIC_LINK_LIBS arrow_testing_static)
    list(APPEND ARROW_FLIGHT_TESTING_SHARED_INSTALL_INTERFACE_LIBS
         ArrowTesting::arrow_testing_static)
    list(APPEND ARROW_FLIGHT_TESTING_STATIC_INSTALL_INTERFACE_LIBS
         ArrowTesting::arrow_testing_static)
  endif()
  list(APPEND ARROW_FLIGHT_TESTING_SHARED_LINK_LIBS ${ARROW_FLIGHT_TEST_INTERFACE_LIBS})
  list(APPEND ARROW_FLIGHT_TESTING_SHARED_LINK_LIBS ${ARROW_GTEST_GMOCK})
  list(APPEND ARROW_FLIGHT_TESTING_STATIC_LINK_LIBS ${ARROW_FLIGHT_TEST_INTERFACE_LIBS})
  list(APPEND ARROW_FLIGHT_TESTING_STATIC_LINK_LIBS ${ARROW_GTEST_GMOCK})
  add_arrow_lib(arrow_flight_testing
                CMAKE_PACKAGE_NAME
                ArrowFlightTesting
                PKG_CONFIG_NAME
                arrow-flight-testing
                OUTPUTS
                ARROW_FLIGHT_TESTING_LIBRARIES
                SOURCES
                test_auth_handlers.cc
                test_definitions.cc
                test_flight_server.cc
                test_util.cc
                DEPENDENCIES
                flight_grpc_gen
                SHARED_LINK_LIBS
                ${ARROW_FLIGHT_TESTING_SHARED_LINK_LIBS}
                SHARED_INSTALL_INTERFACE_LIBS
                ${ARROW_FLIGHT_TESTING_SHARED_INSTALL_INTERFACE_LIBS}
                STATIC_LINK_LIBS
                ${ARROW_FLIGHT_TESTING_STATIC_LINK_LIBS}
                STATIC_INSTALL_INTERFACE_LIBS
                ${ARROW_FLIGHT_TESTING_STATIC_INSTALL_INTERFACE_LIBS}
                PRIVATE_INCLUDES
                "${Protobuf_INCLUDE_DIRS}")

  foreach(LIB_TARGET ${ARROW_FLIGHT_TESTING_LIBRARIES})
    target_compile_definitions(${LIB_TARGET} PRIVATE ARROW_FLIGHT_EXPORTING)
  endforeach()
endif()

add_arrow_test(flight_internals_test
               STATIC_LINK_LIBS
               ${ARROW_FLIGHT_TEST_LINK_LIBS}
               LABELS
               "arrow_flight")

add_arrow_test(flight_test
               STATIC_LINK_LIBS
               ${ARROW_FLIGHT_TEST_LINK_LIBS}
               LABELS
               "arrow_flight")

# Build test server for unit tests or benchmarks
if(ARROW_BUILD_TESTS OR ARROW_BUILD_BENCHMARKS)
  add_executable(flight-test-server test_server.cc)
  target_link_libraries(flight-test-server ${ARROW_FLIGHT_TEST_LINK_LIBS}
                        ${GFLAGS_LIBRARIES})

  if(ARROW_BUILD_TESTS)
    add_dependencies(arrow-flight-test flight-test-server)
  endif()

  add_dependencies(arrow_flight flight-test-server)
endif()

if(ARROW_BUILD_BENCHMARKS)
  # Perf server for benchmarks
  set(PERF_PROTO_GENERATED_FILES "${CMAKE_CURRENT_BINARY_DIR}/perf.pb.cc"
                                 "${CMAKE_CURRENT_BINARY_DIR}/perf.pb.h")

  add_custom_command(OUTPUT ${PERF_PROTO_GENERATED_FILES}
                     COMMAND ${ARROW_PROTOBUF_PROTOC} "-I${CMAKE_CURRENT_SOURCE_DIR}"
                             "--cpp_out=${CMAKE_CURRENT_BINARY_DIR}" "perf.proto"
                     DEPENDS ${PROTO_DEPENDS})

  add_executable(arrow-flight-perf-server perf_server.cc perf.pb.cc)
  target_link_libraries(arrow-flight-perf-server ${ARROW_FLIGHT_TEST_LINK_LIBS}
                        ${GFLAGS_LIBRARIES})

  add_executable(arrow-flight-benchmark flight_benchmark.cc perf.pb.cc)
  target_link_libraries(arrow-flight-benchmark ${ARROW_FLIGHT_TEST_LINK_LIBS}
                        ${GFLAGS_LIBRARIES})

  add_dependencies(arrow-flight-benchmark arrow-flight-perf-server)

  add_dependencies(arrow_flight arrow-flight-benchmark)

endif(ARROW_BUILD_BENCHMARKS)

if(ARROW_FLIGHT_SQL)
  add_subdirectory(sql)

  if(ARROW_BUILD_INTEGRATION)
    add_subdirectory(integration_tests)
  endif()
endif()
