# Copyright (c) Qualcomm Innovation Center, Inc.
# Copyright 2025 Arm Limited and/or its affiliates.
# All rights reserved
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

# Setting this unconditionally used to override the top-level project's choice,
# which broke the Windows build once the vendored c10 started needing C++20 --
# see the note beside CMAKE_CXX_STANDARD in the root CMakeLists.txt. Guarded so
# a configure from the root inherits, and a standalone configure still picks the
# right standard for its platform.
if(NOT CMAKE_CXX_STANDARD)
  if(WIN32)
    set(CMAKE_CXX_STANDARD 20)
  else()
    set(CMAKE_CXX_STANDARD 17)
  endif()
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

#
# get path
#
get_filename_component(
  EXECUTORCH_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE
)
get_filename_component(
  QNN_EXECUTORCH_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR} ABSOLUTE
)
# Let files say "include <executorch/path/to/header.h>".
get_filename_component(
  _common_include_directories "${EXECUTORCH_SOURCE_DIR}/.." ABSOLUTE
)

# If QNN_SDK_ROOT was not passed as a CMake variable, fall back to the
# environment variable.  Prefer downloading the SDK *outside* of CMake (e.g. via
# backends/qualcomm/scripts/download_qnn_sdk.py) and passing the path in.
if(NOT DEFINED QNN_SDK_ROOT AND DEFINED ENV{QNN_SDK_ROOT})
  set(QNN_SDK_ROOT
      $ENV{QNN_SDK_ROOT}
      CACHE PATH "Qualcomm SDK root directory" FORCE
  )
endif()

# Auto-download QNN SDK when QNN_SDK_ROOT was not provided externally. The SDK
# is cached in ~/.cache/executorch/qnn/ so it survives build dir cleans and is
# shared across editable installs, build.sh, and wheel builds.
if(NOT DEFINED QNN_SDK_ROOT)
  message(STATUS "QNN_SDK_ROOT not set. Auto-downloading QNN SDK...")
  execute_process(
    COMMAND
      ${PYTHON_EXECUTABLE}
      ${EXECUTORCH_SOURCE_DIR}/backends/qualcomm/scripts/download_qnn_sdk.py
      --print-sdk-path
    WORKING_DIRECTORY ${EXECUTORCH_SOURCE_DIR}
    RESULT_VARIABLE _qnn_sdk_download_result
    OUTPUT_VARIABLE _qnn_sdk_download_output
    ERROR_VARIABLE _qnn_sdk_download_error
    OUTPUT_STRIP_TRAILING_WHITESPACE
  )
  if(NOT _qnn_sdk_download_result EQUAL 0 OR _qnn_sdk_download_output STREQUAL
                                             ""
  )
    message(
      FATAL_ERROR
        "Failed to download Qualcomm SDK.\n"
        "stderr: ${_qnn_sdk_download_error}\n"
        "Set QNN_SDK_ROOT manually, e.g. cmake <..> -DQNN_SDK_ROOT=<...>"
    )
  endif()
  set(QNN_SDK_ROOT
      ${_qnn_sdk_download_output}
      CACHE PATH "Qualcomm SDK root directory" FORCE
  )
  set(ENV{QNN_SDK_ROOT} ${QNN_SDK_ROOT})
endif()

if(CMAKE_TOOLCHAIN_FILE MATCHES ".*(iOS|ios\.toolchain)\.cmake$")
  message(FATAL_ERROR "ios is not supported by Qualcomm AI Engine Direct")
endif()

message(STATUS "Using qnn sdk root ${QNN_SDK_ROOT}")
message(STATUS "Using EXECUTORCH_SOURCE_DIR ${EXECUTORCH_SOURCE_DIR}")

if(${ANDROID})
  find_library(android_log log)
endif()

if(MSVC)
  add_compile_options("/wd4996")
else()
  add_compile_options("-Wall" "-Werror" "-fvisibility=hidden")
endif()
add_compile_definitions(C10_USING_CUSTOM_GENERATED_MACROS)

# GNU emits warning for ignored attributes Unfortunately, we use
# [[maybe_unused]] which can be ignored by GNU. So we make it a warning, not an
# error in GNU.
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
  add_compile_options("-Wno-error=attributes")
  add_link_options("-flto=auto")
endif()

if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
  if(MSVC)
    add_compile_options("/O2")
  else()
    # strip symbols
    add_link_options(LINKER:-s,--gc-sections)
    if(${CMAKE_SYSTEM_PROCESSOR} MATCHES Hexagon)
      add_compile_options(
        "-Os"
        "-ffunction-sections"
        "-fdata-sections"
        "-frtti"
        "-fno-exceptions"
        "-fomit-frame-pointer"
        "-fno-asynchronous-unwind-tables"
      )
    else()
      add_compile_options(
        "-O3" "-ffunction-sections" "-fdata-sections" "-frtti"
      )
    endif()
  endif()
endif()

include_directories(
  BEFORE ${_common_include_directories} ${QNN_SDK_ROOT}/include/QNN
  ${EXECUTORCH_SOURCE_DIR}/runtime/core/portable_type/c10
  ${QNN_EXECUTORCH_ROOT_DIR}/runtime/pal/include
)

set(_qnn_schema__srcs backends/qualcomm/serialization/qc_compiler_spec.fbs)
set(_qnn_schema__include_dir "${CMAKE_BINARY_DIR}/schema/include")
# Paths to headers generated from the .fbs files.
set(_qnn_schema__outputs)
foreach(fbs_file ${_qnn_schema__srcs})
  string(REGEX REPLACE "serialization/([^/]+)[.]fbs$" "\\1_generated.h"
                       generated "${fbs_file}"
  )
  list(APPEND _qnn_schema__outputs
       "${_qnn_schema__include_dir}/executorch/${generated}"
  )
endforeach()

# Generate the headers from the .fbs files.
add_custom_command(
  OUTPUT ${_qnn_schema__outputs}
  COMMAND
    flatc --cpp --cpp-std c++11 --scoped-enums -o
    "${_qnn_schema__include_dir}/executorch/backends/qualcomm"
    ${_qnn_schema__srcs}
  WORKING_DIRECTORY ${EXECUTORCH_SOURCE_DIR}
  DEPENDS flatc
  COMMENT "Generating qnn_schema headers"
  VERBATIM
)

include_directories(
  BEFORE ${_qnn_schema__include_dir}
  ${EXECUTORCH_SOURCE_DIR}/third-party/flatbuffers/include
)

#
# declare targets
#
add_library(executorch_backend INTERFACE)
add_library(qnn_backend STATIC)
add_library(qnn_backend_cache STATIC)
add_library(qnn_backend_options STATIC)
add_library(qnn_context STATIC)
add_library(qnn_custom_protocol STATIC)
add_library(qnn_dlc_manager STATIC)
add_library(qnn_device STATIC)
add_library(qnn_executorch_backend SHARED)
add_library(qnn_executorch_header INTERFACE)
add_library(qnn_executorch_logging STATIC)
add_library(qnn_factory STATIC)
add_library(qnn_backend_unified_registry STATIC)
add_library(qnn_function_interface INTERFACE)
add_library(qnn_graph STATIC)
add_library(qnn_implementation STATIC)
add_library(qnn_logger STATIC)
add_library(qnn_manager STATIC)
add_library(qnn_mem_manager STATIC)
add_library(qnn_op_package_manager STATIC)
add_library(qnn_pal STATIC)
add_library(qnn_profiler STATIC)
add_library(qnn_schema INTERFACE ${_qnn_schema__outputs})
add_library(qnn_sys_function_interface INTERFACE)
add_library(qnn_sys_implementation STATIC)
add_library(shared_buffer STATIC)
add_library(wrappers STATIC)

#
# declare dependency
#
target_link_libraries(wrappers PRIVATE qnn_executorch_logging)
target_link_libraries(
  qnn_implementation PRIVATE qnn_function_interface qnn_executorch_logging
                             qnn_pal ${CMAKE_DL_LIBS}
)
target_link_libraries(
  qnn_sys_implementation
  PRIVATE qnn_sys_function_interface qnn_executorch_logging qnn_pal
          ${CMAKE_DL_LIBS}
)
target_link_libraries(qnn_executorch_logging PRIVATE qnn_schema)
target_link_libraries(qnn_profiler PRIVATE qnn_executorch_logging)
target_link_libraries(qnn_logger PRIVATE qnn_implementation ${android_log})
target_link_libraries(
  qnn_backend PRIVATE qnn_implementation qnn_logger qnn_op_package_manager
)
target_link_libraries(qnn_custom_protocol PRIVATE qnn_logger)
target_link_libraries(qnn_backend_options PRIVATE qnn_schema)
target_link_libraries(
  qnn_device PRIVATE qnn_executorch_logging qnn_implementation qnn_logger
)
target_link_libraries(qnn_backend_cache PRIVATE qnn_sys_implementation)
target_link_libraries(
  qnn_context PRIVATE qnn_implementation qnn_logger qnn_backend qnn_device
                      qnn_backend_cache
)
target_link_libraries(
  qnn_graph PRIVATE qnn_executorch_logging qnn_implementation qnn_context
                    qnn_profiler
)
target_link_libraries(
  qnn_mem_manager PRIVATE qnn_executorch_logging qnn_implementation qnn_context
)

target_link_libraries(
  qnn_factory
  PRIVATE qnn_schema
          qnn_backend
          qnn_device
          qnn_context
          qnn_graph
          qnn_mem_manager
          qnn_custom_protocol
)

target_link_libraries(
  qnn_backend_unified_registry PRIVATE qnn_schema qnn_backend qnn_device
                                       qnn_implementation
)

target_link_libraries(
  qnn_dlc_manager
  PRIVATE qnn_factory
          qnn_backend_unified_registry
          qnn_backend
          qnn_device
          qnn_context
          qnn_graph
          qnn_mem_manager
          qnn_pal
)

target_link_libraries(
  qnn_manager PRIVATE qnn_factory qnn_backend_unified_registry wrappers
                      qnn_schema shared_buffer qnn_dlc_manager
)
target_link_libraries(
  qnn_executorch_backend PRIVATE qnn_executorch_header qnn_schema qnn_manager
                                 qnn_backend_options
)
# Resolve the runtime from the shared library when one is built, so this
# delegate does not carry its own copy of the backend registry.
if(EXECUTORCH_BUILD_SHARED)
  target_link_libraries(qnn_executorch_backend PRIVATE executorch_shared)
  executorch_target_link_shared_runtime(qnn_executorch_backend)
  # Named like the other shipped delegates, so a consumer of the wheel finds it
  # where it finds them and the package config needs no special case. Only the
  # shared build renames: the device and direct-mode builds keep the original
  # name, which their own tooling and the signing step expect.
  set_target_properties(
    qnn_executorch_backend PROPERTIES OUTPUT_NAME executorch_backend_qnn
  )
  executorch_target_shared_runtime_path(
    qnn_executorch_backend "lib" "${CMAKE_INSTALL_LIBDIR}"
  )
else()
  target_link_libraries(qnn_executorch_backend PRIVATE executorch_core)
endif()

if(${CMAKE_SYSTEM_PROCESSOR} MATCHES Hexagon)
  # Add macro here so we can dlopen the correct .so library.
  if(DSP_TYPE STREQUAL "0")
    message(
      STATUS
        "aDSP direct and non-direct mode uses shared libraries under different folders but have the same name, skipping HEXAGON_LIB override."
    )
    # Just random string here since QnnBackendUnifiedRegistry.h will ignore this
    # macro for aDSP case.
    add_compile_definitions(HEXAGON_LIB="")
  elseif(DSP_TYPE STREQUAL "3")
    string(TOUPPER ${CDSP_VERSION} CAPITAL_CDSP_VERSION)
    set(HEXAGON_LIB "libQnnHtp${CAPITAL_CDSP_VERSION}.so")
    add_compile_definitions(HEXAGON_LIB="${HEXAGON_LIB}")
    message(STATUS "For hexagon build, using HTP Library: ${HEXAGON_LIB}")
  else()
    message(FATAL_ERROR "Unknown DSP_TYPE ${DSP_TYPE}")
  endif()

  # aDSP will also use cDSP c/c++ library since aDSP does not have these
  # libraries.
  target_link_libraries(
    qnn_executorch_backend
    PRIVATE
      ${HEXAGON_TOOLS_ROOT}/Tools/target/hexagon/lib/${CDSP_VERSION}/G0/pic/libc.so
      ${HEXAGON_TOOLS_ROOT}/Tools/target/hexagon/lib/${CDSP_VERSION}/G0/pic/libc++.so.1
      ${HEXAGON_TOOLS_ROOT}/Tools/target/hexagon/lib/${CDSP_VERSION}/G0/pic/libc++abi.so.1
  )
endif()

if(MSVC)
  target_compile_definitions(
    qnn_executorch_backend PRIVATE QNN_EXECUTORCH_BUILDING_DLL
  )
  set_target_properties(
    qnn_executorch_backend PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON
  )
else()
  set_target_properties(
    qnn_executorch_backend PROPERTIES LINK_FLAGS "-Wl,-rpath='$ORIGIN'"
  )
endif()
target_link_libraries(
  shared_buffer PRIVATE qnn_executorch_logging qnn_pal ${CMAKE_DL_LIBS}
)
#
# add linker option
#
if(NOT MSVC)
  executorch_target_link_options_shared_lib(qnn_executorch_backend)
endif()

#
# add sources
#
add_subdirectory(
  ${QNN_EXECUTORCH_ROOT_DIR}/runtime ${CMAKE_CURRENT_BINARY_DIR}/qnn_executorch
)
add_subdirectory(
  ${QNN_EXECUTORCH_ROOT_DIR}/runtime/backends
  ${CMAKE_CURRENT_BINARY_DIR}/qnn_executorch/backends
)
add_subdirectory(
  ${QNN_EXECUTORCH_ROOT_DIR}/aot/wrappers
  ${CMAKE_CURRENT_BINARY_DIR}/qnn_executorch/wrappers
)
# The shared build installs beside the other delegates; every other build keeps
# the original location, which the device tooling and the exporter's push list
# expect.
if(EXECUTORCH_BUILD_SHARED)
  set(_qnn_install_dir ${CMAKE_INSTALL_LIBDIR})
else()
  set(_qnn_install_dir ${CMAKE_INSTALL_LIBDIR}/executorch/backends/qualcomm)
endif()
install(
  TARGETS qnn_executorch_backend
  EXPORT ExecuTorchTargets
  LIBRARY DESTINATION ${_qnn_install_dir}
  ARCHIVE DESTINATION ${_qnn_install_dir}
  RUNTIME DESTINATION ${_qnn_install_dir}
)

if(${CMAKE_SYSTEM_PROCESSOR} MATCHES Hexagon)
  add_subdirectory(
    ${QNN_EXECUTORCH_ROOT_DIR}/runtime/backends/direct_mode
    ${CMAKE_CURRENT_BINARY_DIR}/qnn_executorch/direct_mode
  )
endif()

# QNN pybind
if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64|AMD64")
  add_subdirectory(
    ${EXECUTORCH_SOURCE_DIR}/third-party/pybind11
    ${CMAKE_CURRENT_BINARY_DIR}/pybind11
  )
  add_library(PyQnnManagerAdaptor MODULE)
  # PyQnnManager containing a pybind type triggers the warning because pybind11
  # code internally forces hidden visibility.
  set_target_properties(
    PyQnnManagerAdaptor PROPERTIES CXX_VISIBILITY_PRESET hidden
  )

  target_link_libraries(
    PyQnnManagerAdaptor
    PRIVATE pybind11::module
            pybind11::lto
            qnn_schema
            qnn_manager
            qnn_executorch_header
            qnn_backend_options
            wrappers
            qnn_executorch_logging
  )
  # extension_tensor is bundled into the shared runtime, so naming it again here
  # would give this module a second copy of what that library already provides.
  if(NOT EXECUTORCH_BUILD_SHARED)
    target_link_libraries(PyQnnManagerAdaptor PRIVATE extension_tensor)
  endif()
  # Same reasoning as the delegate above: take the runtime from the shared
  # library when there is one, rather than embedding a second registry.
  if(EXECUTORCH_BUILD_SHARED)
    target_link_libraries(PyQnnManagerAdaptor PRIVATE executorch_shared)
    executorch_target_link_shared_runtime(PyQnnManagerAdaptor)
    executorch_target_shared_runtime_path(
      PyQnnManagerAdaptor "backends/qualcomm/python"
      "${CMAKE_INSTALL_LIBDIR}/executorch/backends/qualcomm/python"
    )
  else()
    target_link_libraries(PyQnnManagerAdaptor PRIVATE executorch)
  endif()

  pybind11_extension(PyQnnManagerAdaptor)
  if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES RelWithDebInfo)
    # Strip unnecessary sections of the binary
    pybind11_strip(PyQnnManagerAdaptor)
  endif()

  if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
    # need to allow exceptions in pybind
    if(MSVC)
      set(_pybind_compile_options /wd4996 /EHsc)
    else()
      set(_pybind_compile_options -Wno-deprecated-declarations -fPIC -frtti
                                  -fexceptions
      )
    endif()
    target_compile_options(
      PyQnnManagerAdaptor PUBLIC ${_pybind_compile_options}
    )
  endif()

  add_subdirectory(
    ${QNN_EXECUTORCH_ROOT_DIR}/aot/python
    ${CMAKE_CURRENT_BINARY_DIR}/qnn_executorch/python
  )

  install(
    TARGETS PyQnnManagerAdaptor
    LIBRARY
      DESTINATION ${CMAKE_INSTALL_LIBDIR}/executorch/backends/qualcomm/python
    RUNTIME
      DESTINATION ${CMAKE_INSTALL_LIBDIR}/executorch/backends/qualcomm/python
  )
endif()
