cmake_minimum_required(VERSION 3.20)
project(ppocr_cpp VERSION 5.0.0 LANGUAGES C CXX)

include(GNUInstallDirs)
include(CMakePackageConfigHelpers)

option(PPOCR_BUILD_SHARED "Build the shared library" ON)
option(PPOCR_BUILD_STATIC "Build the static library" ON)
option(PPOCR_BUILD_EXAMPLES "Build examples and smoke tools" ON)
option(PPOCR_BUILD_TESTS "Build the C API smoke test" ON)
# Shaders are compiled into the library. The loader is dlopen'd, so this does
# not add a libvulkan DT_NEEDED entry. Wheels pass OFF: manylinux images do
# not ship glslang, and the published CPU wheel must not change when the
# image happens to contain a shader compiler.
option(PPOCR_ENABLE_VULKAN "Embed Vulkan compute shaders when headers and glslangValidator are available" ON)
# Native Metal execution of the same GPU graph on Apple platforms. The GLSL
# kernels are compiled to SPIR-V by glslang and translated to MSL by
# SPIRV-Cross at build time; Metal compiles them at runtime. It needs
# third_party/Vulkan-Headers, glslangValidator and spirv-cross on the build
# machine and adds no runtime dependency beyond Metal.framework.
if(APPLE)
  set(PPOCR_METAL_DEFAULT ON)
else()
  set(PPOCR_METAL_DEFAULT OFF)
endif()
option(PPOCR_ENABLE_METAL "Build the native Metal GPU backend (Apple only)" ${PPOCR_METAL_DEFAULT})

if(NOT PPOCR_BUILD_SHARED AND NOT PPOCR_BUILD_STATIC)
  message(FATAL_ERROR "Enable PPOCR_BUILD_SHARED and/or PPOCR_BUILD_STATIC")
endif()

set(PPOCR_SOURCES
    src/kernels.cpp
    src/onnx_lite.cpp
    src/ppocr.cpp
    src/vulkan_backend.cpp
    src/ppocr_c_api.cpp
    src/db_geometry.cpp
    src/cv_compat.cpp
    src/clipper/clipper.cpp
)

add_library(ppocr_objects OBJECT ${PPOCR_SOURCES})
set_target_properties(ppocr_objects PROPERTIES
    POSITION_INDEPENDENT_CODE ON
    CXX_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN ON)
target_compile_definitions(ppocr_objects PRIVATE PPOCR_BUILDING_SHARED)
target_include_directories(ppocr_objects PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_compile_features(ppocr_objects PUBLIC cxx_std_20)

if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/Vulkan-Headers/include/vulkan/vulkan.h")
  target_include_directories(ppocr_objects PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/Vulkan-Headers/include)
  target_compile_definitions(ppocr_objects PRIVATE PPOCR_HAS_VULKAN_HEADERS=1)
endif()
# The vendored glslang.exe is a Windows compiler. On Linux/ARM use a host
# glslangValidator so the build does not try to execute a PE binary.
if(WIN32 AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/glslang/bin/glslang.exe")
  set(PPOCR_GLSLANG "${CMAKE_CURRENT_SOURCE_DIR}/third_party/glslang/bin/glslang.exe")
else()
  find_program(PPOCR_GLSLANG NAMES glslangValidator glslang)
endif()
option(PPOCR_REQUIRE_METAL "Fail configure when the Metal backend cannot be built" OFF)
set(PPOCR_METAL_ACTIVE OFF)
if(PPOCR_ENABLE_METAL AND APPLE)
  find_program(PPOCR_SPIRV_CROSS NAMES spirv-cross)
  if(PPOCR_SPIRV_CROSS AND PPOCR_GLSLANG AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/Vulkan-Headers/include/vulkan/vulkan.h")
    set(PPOCR_METAL_ACTIVE ON)
  else()
    message(STATUS "Metal backend disabled: need Vulkan-Headers, glslangValidator and spirv-cross (glslang='${PPOCR_GLSLANG}' spirv-cross='${PPOCR_SPIRV_CROSS}')")
    if(PPOCR_REQUIRE_METAL)
      message(FATAL_ERROR "PPOCR_REQUIRE_METAL is ON but the Metal backend cannot be built")
    endif()
  endif()
elseif(PPOCR_ENABLE_METAL)
  message(STATUS "Metal backend is only built on Apple platforms")
endif()
if((PPOCR_ENABLE_VULKAN OR PPOCR_METAL_ACTIVE) AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/Vulkan-Headers/include/vulkan/vulkan.h" AND PPOCR_GLSLANG)
  set(PPOCR_VULKAN_GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
  set(PPOCR_VULKAN_SPV "${PPOCR_VULKAN_GENERATED_DIR}/vulkan_binary.spv")
  set(PPOCR_VULKAN_SPV_HEADER "${PPOCR_VULKAN_GENERATED_DIR}/vulkan_binary_spv.hpp")
  set(PPOCR_VULKAN_TAIL_SPV "${PPOCR_VULKAN_GENERATED_DIR}/vulkan_depthwise_tail.spv")
  set(PPOCR_VULKAN_TAIL_SPV_HEADER "${PPOCR_VULKAN_GENERATED_DIR}/vulkan_depthwise_tail_spv.hpp")
  set(PPOCR_VULKAN_POINTWISE_TAIL_SPV "${PPOCR_VULKAN_GENERATED_DIR}/vulkan_pointwise_tail.spv")
  set(PPOCR_VULKAN_POINTWISE_TAIL_SPV_HEADER "${PPOCR_VULKAN_GENERATED_DIR}/vulkan_pointwise_tail_spv.hpp")
  add_custom_command(
    OUTPUT "${PPOCR_VULKAN_SPV}" "${PPOCR_VULKAN_SPV_HEADER}" "${PPOCR_VULKAN_TAIL_SPV}" "${PPOCR_VULKAN_TAIL_SPV_HEADER}" "${PPOCR_VULKAN_POINTWISE_TAIL_SPV}" "${PPOCR_VULKAN_POINTWISE_TAIL_SPV_HEADER}"
    COMMAND ${CMAKE_COMMAND} -E make_directory "${PPOCR_VULKAN_GENERATED_DIR}"
    COMMAND "${PPOCR_GLSLANG}" -V -S comp -o "${PPOCR_VULKAN_SPV}" "${CMAKE_CURRENT_SOURCE_DIR}/src/vulkan_binary.comp"
    COMMAND "${PPOCR_GLSLANG}" -V -S comp -o "${PPOCR_VULKAN_TAIL_SPV}" "${CMAKE_CURRENT_SOURCE_DIR}/src/vulkan_depthwise_tail.comp"
    COMMAND "${PPOCR_GLSLANG}" -V -S comp -o "${PPOCR_VULKAN_POINTWISE_TAIL_SPV}" "${CMAKE_CURRENT_SOURCE_DIR}/src/vulkan_pointwise_tail.comp"
    COMMAND ${CMAKE_COMMAND} -DINPUT=${PPOCR_VULKAN_SPV} -DOUTPUT=${PPOCR_VULKAN_SPV_HEADER} -DNAME=kVulkanBinarySpv -P "${CMAKE_CURRENT_SOURCE_DIR}/src/embed_spirv.cmake"
    COMMAND ${CMAKE_COMMAND} -DINPUT=${PPOCR_VULKAN_TAIL_SPV} -DOUTPUT=${PPOCR_VULKAN_TAIL_SPV_HEADER} -DNAME=kVulkanDepthwiseTailSpv -P "${CMAKE_CURRENT_SOURCE_DIR}/src/embed_spirv.cmake"
    COMMAND ${CMAKE_COMMAND} -DINPUT=${PPOCR_VULKAN_POINTWISE_TAIL_SPV} -DOUTPUT=${PPOCR_VULKAN_POINTWISE_TAIL_SPV_HEADER} -DNAME=kVulkanPointwiseTailSpv -P "${CMAKE_CURRENT_SOURCE_DIR}/src/embed_spirv.cmake"
    COMMAND ${CMAKE_COMMAND} -E touch "${PPOCR_VULKAN_SPV_HEADER}"
    COMMAND ${CMAKE_COMMAND} -E touch "${PPOCR_VULKAN_TAIL_SPV_HEADER}"
    COMMAND ${CMAKE_COMMAND} -E touch "${PPOCR_VULKAN_POINTWISE_TAIL_SPV_HEADER}"
    DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/vulkan_binary.comp" "${CMAKE_CURRENT_SOURCE_DIR}/src/vulkan_depthwise_tail.comp" "${CMAKE_CURRENT_SOURCE_DIR}/src/vulkan_pointwise_tail.comp" "${CMAKE_CURRENT_SOURCE_DIR}/src/embed_spirv.cmake" "${PPOCR_GLSLANG}"
    VERBATIM)
  add_custom_target(ppocr_vulkan_shader DEPENDS "${PPOCR_VULKAN_SPV_HEADER}" "${PPOCR_VULKAN_TAIL_SPV_HEADER}" "${PPOCR_VULKAN_POINTWISE_TAIL_SPV_HEADER}")
  add_dependencies(ppocr_objects ppocr_vulkan_shader)
  target_include_directories(ppocr_objects PRIVATE "${PPOCR_VULKAN_GENERATED_DIR}")
  target_compile_definitions(ppocr_objects PRIVATE PPOCR_HAS_VULKAN_KERNELS=1)
  if(PPOCR_ENABLE_VULKAN)
    target_compile_definitions(ppocr_objects PRIVATE PPOCR_VULKAN_LOADER=1)
  endif()
  if(PPOCR_METAL_ACTIVE)
    enable_language(OBJCXX)
    set(PPOCR_METAL_SOURCES "")
    foreach(shader binary depthwise_tail pointwise_tail)
      if(shader STREQUAL "binary")
        set(name kMetalBinaryMsl)
      elseif(shader STREQUAL "depthwise_tail")
        set(name kMetalDepthwiseTailMsl)
      else()
        set(name kMetalPointwiseTailMsl)
      endif()
      set(raw "${PPOCR_VULKAN_GENERATED_DIR}/metal_${shader}.raw.metal")
      set(msl "${PPOCR_VULKAN_GENERATED_DIR}/metal_${shader}.metal")
      set(header "${PPOCR_VULKAN_GENERATED_DIR}/metal_${shader}_msl.hpp")
      add_custom_command(
        OUTPUT "${header}"
        COMMAND "${PPOCR_SPIRV_CROSS}" --msl --msl-version 20400 --msl-decoration-binding
                --output "${raw}" "${PPOCR_VULKAN_GENERATED_DIR}/vulkan_${shader}.spv"
        COMMAND ${CMAKE_COMMAND} -DINPUT=${raw} -DOUTPUT=${msl} -P "${CMAKE_CURRENT_SOURCE_DIR}/src/metal_patch_msl.cmake"
        COMMAND ${CMAKE_COMMAND} -DINPUT=${msl} -DOUTPUT=${header} -DNAME=${name} -P "${CMAKE_CURRENT_SOURCE_DIR}/src/embed_spirv.cmake"
        DEPENDS "${PPOCR_VULKAN_SPV_HEADER}" "${PPOCR_VULKAN_TAIL_SPV_HEADER}" "${PPOCR_VULKAN_POINTWISE_TAIL_SPV_HEADER}"
                "${CMAKE_CURRENT_SOURCE_DIR}/src/metal_patch_msl.cmake" "${PPOCR_SPIRV_CROSS}"
        VERBATIM)
      list(APPEND PPOCR_METAL_SOURCES "${header}")
    endforeach()
    add_custom_target(ppocr_metal_shader DEPENDS ${PPOCR_METAL_SOURCES})
    add_dependencies(ppocr_metal_shader ppocr_vulkan_shader)
    add_dependencies(ppocr_objects ppocr_metal_shader)
    target_sources(ppocr_objects PRIVATE src/metal_driver.mm)
    set_source_files_properties(src/metal_driver.mm PROPERTIES COMPILE_OPTIONS "-fobjc-arc;-fno-fast-math")
    target_compile_definitions(ppocr_objects PRIVATE PPOCR_HAS_METAL=1)
    set(PPOCR_METAL_LINK "-framework Metal" "-framework Foundation")
    message(STATUS "Metal backend enabled (${PPOCR_SPIRV_CROSS})")
  endif()
elseif(NOT PPOCR_ENABLE_VULKAN)
  message(STATUS "Vulkan compute shaders disabled by PPOCR_ENABLE_VULKAN=OFF")
else()
  message(STATUS "Vulkan compute shaders disabled: need Vulkan-Headers and glslangValidator")
endif()

if(MSVC)
  target_compile_options(ppocr_objects PRIVATE /W4 /permissive- /O2 /fp:fast)
else()
  target_compile_options(ppocr_objects PRIVATE -Wall -Wextra -Wpedantic -O3 -ffast-math -funroll-loops
      -U__FINITE_MATH_ONLY__ -D__FINITE_MATH_ONLY__=0)
  # -ffast-math defines __FINITE_MATH_ONLY__=1, and old glibc headers
  # (manylinux2014, glibc 2.17) then redirect expf/powf/hypot to __*_finite.
  # glibc >= 2.31 no longer lets new programs link those, so libppocr.a was
  # unusable on current distros. Resetting only the macro keeps the fast-math
  # code generation (bit-identical results) and calls the plain functions;
  # -fno-finite-math-only would also change codegen and score bits.
endif()

string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR};${CMAKE_GENERATOR_PLATFORM}" PPOCR_CPU)
if(NOT MSVC AND PPOCR_CPU MATCHES "x86_64|amd64|x64|i[3-6]86")
  # Baseline translation units stay SSE2. Auto-vectorization must not emit
  # AVX or AVX-512, or an AVX2-only machine will SIGILL inside the fallback.
  set_source_files_properties(
      src/kernels.cpp src/onnx_lite.cpp src/ppocr.cpp src/vulkan_backend.cpp src/ppocr_c_api.cpp
      PROPERTIES COMPILE_OPTIONS "-mno-avx;-mno-avx2;-mno-avx512f")
endif()

# DB postprocess geometry and the OpenCV crop/resize port (cv_compat)
# reproduce OpenCV/pyclipper float results bit for bit, so these files opt
# out of fast-math and FMA contraction.
if(MSVC)
  set_source_files_properties(src/db_geometry.cpp src/cv_compat.cpp
      PROPERTIES COMPILE_OPTIONS "/fp:precise")
  set_source_files_properties(src/clipper/clipper.cpp
      PROPERTIES COMPILE_OPTIONS "/fp:precise;/w")
else()
  # FMA contraction follows the compiler default OpenCV wheels are built
  # with: none on baseline x86, "on" for Clang (Apple arm64), "fast" for GCC.
  if(PPOCR_CPU MATCHES "x86_64|amd64|x64|i[3-6]86")
    set(PPOCR_STRICT_FP "-fno-fast-math;-ffp-contract=off;-mno-avx;-mno-avx2;-mno-avx512f")
  elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    set(PPOCR_STRICT_FP "-fno-fast-math;-ffp-contract=on")
  else()
    set(PPOCR_STRICT_FP "-fno-fast-math;-ffp-contract=fast")
  endif()
  set_source_files_properties(src/db_geometry.cpp src/cv_compat.cpp
      PROPERTIES COMPILE_OPTIONS "${PPOCR_STRICT_FP}")
  # Vendored Clipper 6.4.2 is kept verbatim; silence its old-style warnings.
  set_source_files_properties(src/clipper/clipper.cpp
      PROPERTIES COMPILE_OPTIONS "${PPOCR_STRICT_FP};-w")
endif()

# AVX2 is isolated in its own object file. kernels.cpp remains baseline x86,
# so CPUs without AVX/AVX2 can safely take its scalar fallback at runtime.
if(PPOCR_CPU MATCHES "loongarch64" AND NOT MSVC)
  # LSX is the la64v1.0 baseline (NEON code paths via src/neon_lsx_shim.hpp);
  # LASX kernels live in their own TU and are entered after a HWCAP check.
  target_sources(ppocr_objects PRIVATE src/kernels_lasx.cpp)
  set_source_files_properties(src/kernels_lasx.cpp PROPERTIES COMPILE_OPTIONS "-mlasx")
  target_compile_definitions(ppocr_objects PRIVATE PPOCR_HAS_LASX_KERNELS=1)
endif()
if(PPOCR_CPU MATCHES "x86_64|amd64|x64|i[3-6]86")
  target_sources(ppocr_objects PRIVATE src/kernels_avx2.cpp src/cv_compat_avx2.cpp)
  target_compile_definitions(ppocr_objects PRIVATE PPOCR_HAS_AVX2_KERNELS=1)
  if(MSVC)
    set_source_files_properties(src/kernels_avx2.cpp PROPERTIES COMPILE_OPTIONS "/arch:AVX2;/fp:fast")
    set_source_files_properties(src/cv_compat_avx2.cpp PROPERTIES COMPILE_OPTIONS "/arch:AVX2;/fp:precise")
  else()
    # OpenCV's AVX2 bicubic kernel, rounding step for rounding step.
    set_source_files_properties(src/cv_compat_avx2.cpp PROPERTIES COMPILE_OPTIONS
        "-mavx2;-mfma;-mno-avx512f;-fno-fast-math;-ffp-contract=off")
    # -mno-avx512* is last-wins together with -mavx2 and forbids the compiler
    # from rewriting this file with EVEX/zmm encodings.
    set_source_files_properties(src/kernels_avx2.cpp PROPERTIES COMPILE_OPTIONS
        "-mavx2;-mfma;-mno-avx512f;-mno-avx512bw;-mno-avx512vl;-mno-avx512dq")
  endif()
  # 32-bit MSVC builds compile the AVX-512 kernels but they produce wrong
  # results there (truncated recognition), so x86 (win32) keeps AVX2 + scalar.
  if(CMAKE_SIZEOF_VOID_P EQUAL 4)
    message(STATUS "AVX-512 kernels disabled for this 32-bit build")
  else()
  target_sources(ppocr_objects PRIVATE src/kernels_avx512.cpp)
  target_compile_definitions(ppocr_objects PRIVATE PPOCR_HAS_AVX512_KERNELS=1)
  endif()
  if(MSVC)
    set_source_files_properties(src/kernels_avx512.cpp PROPERTIES COMPILE_OPTIONS "/arch:AVX512;/fp:fast")
  else()
    # This TU uses AVX-512F/DQ/BW/VL plus BMI tzcnt. None of those encodings
    # are enabled in kernels.cpp or kernels_avx2.cpp. HasAvx512() requires
    # the same feature bits before the TU is entered.
    set_source_files_properties(src/kernels_avx512.cpp PROPERTIES COMPILE_OPTIONS
        "-mavx512f;-mavx512dq;-mavx512bw;-mavx512vl;-mbmi;-mbmi2;-mfma")
  endif()
endif()

# Apple Silicon matrix units (AMX/SME) are reachable from user code only via
# Accelerate. Pointwise convolutions and the CTC head use cblas_sgemm there.
option(PPOCR_USE_ACCELERATE "Use Apple Accelerate for CPU GEMM on Apple platforms" ON)
if(APPLE AND PPOCR_USE_ACCELERATE)
  target_compile_definitions(ppocr_objects PRIVATE PPOCR_USE_ACCELERATE=1)
  list(APPEND PPOCR_METAL_LINK "-framework Accelerate")
endif()

set(PPOCR_INSTALL_TARGETS "")

if(PPOCR_BUILD_STATIC)
  add_library(ppocr STATIC $<TARGET_OBJECTS:ppocr_objects>)
  target_include_directories(ppocr PUBLIC
      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
      $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
  target_compile_features(ppocr PUBLIC cxx_std_20)
  if(UNIX AND NOT APPLE)
    target_link_libraries(ppocr PUBLIC ${CMAKE_DL_LIBS})
  endif()
  # Consumers of the static archive need the thread library (glibc < 2.34).
  find_package(Threads REQUIRED)
  target_link_libraries(ppocr PUBLIC Threads::Threads)
  if(PPOCR_METAL_LINK)
    target_link_libraries(ppocr PUBLIC ${PPOCR_METAL_LINK})
  endif()
  set_target_properties(ppocr PROPERTIES OUTPUT_NAME ppocr EXPORT_NAME static)
  if(WIN32)
    set_target_properties(ppocr PROPERTIES OUTPUT_NAME ppocr_static)
  endif()
  add_library(ppocr::static ALIAS ppocr)
  list(APPEND PPOCR_INSTALL_TARGETS ppocr)
endif()

if(PPOCR_BUILD_SHARED)
  add_library(ppocr_shared SHARED $<TARGET_OBJECTS:ppocr_objects>)
  target_include_directories(ppocr_shared PUBLIC
      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
      $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
  target_compile_features(ppocr_shared PUBLIC cxx_std_20)
  target_compile_definitions(ppocr_shared PUBLIC PPOCR_SHARED)
  if(UNIX AND NOT APPLE)
    target_link_libraries(ppocr_shared PRIVATE ${CMAKE_DL_LIBS})
  endif()
  if(PPOCR_METAL_LINK)
    target_link_libraries(ppocr_shared PRIVATE ${PPOCR_METAL_LINK})
  endif()
  set_target_properties(ppocr_shared PROPERTIES
      OUTPUT_NAME ppocr
      EXPORT_NAME ppocr
      VERSION ${PROJECT_VERSION}
      SOVERSION ${PROJECT_VERSION_MAJOR})
  add_library(ppocr::ppocr ALIAS ppocr_shared)
  list(APPEND PPOCR_INSTALL_TARGETS ppocr_shared)
endif()

function(ppocr_link_runtime target)
  if(PPOCR_BUILD_STATIC)
    target_link_libraries(${target} PRIVATE ppocr)
  else()
    target_link_libraries(${target} PRIVATE ppocr_shared)
  endif()
endfunction()

# Smoke tools call detail:: symbols. Those symbols are hidden in the shared
# library, so the tools link the object files instead.
function(ppocr_link_objects target)
  target_link_libraries(${target} PRIVATE $<TARGET_OBJECTS:ppocr_objects>)
  target_include_directories(${target} PRIVATE
      "${CMAKE_CURRENT_SOURCE_DIR}/include"
      "${CMAKE_CURRENT_SOURCE_DIR}/src")
  target_compile_features(${target} PRIVATE cxx_std_20)
  if(UNIX AND NOT APPLE)
    target_link_libraries(${target} PRIVATE ${CMAKE_DL_LIBS})
  endif()
  if(PPOCR_METAL_LINK)
    target_link_libraries(${target} PRIVATE ${PPOCR_METAL_LINK})
  endif()
endfunction()

if(PPOCR_BUILD_EXAMPLES)
  add_executable(ppocr_demo examples/ppocr_demo.cpp)
  ppocr_link_runtime(ppocr_demo)

  add_executable(ppocr_cpp_api_demo examples/cpp_api_demo.cpp)
  target_link_libraries(ppocr_cpp_api_demo PRIVATE $<IF:$<BOOL:${PPOCR_BUILD_SHARED}>,ppocr_shared,ppocr>)
  if(PPOCR_BUILD_SHARED)
    set_target_properties(ppocr_cpp_api_demo PROPERTIES BUILD_RPATH "$<TARGET_FILE_DIR:ppocr_shared>")
  endif()

  add_executable(ppocr_c_api_demo examples/c_api_demo.c)
  target_link_libraries(ppocr_c_api_demo PRIVATE $<IF:$<BOOL:${PPOCR_BUILD_SHARED}>,ppocr_shared,ppocr>)
  if(PPOCR_BUILD_SHARED)
    set_target_properties(ppocr_c_api_demo PROPERTIES BUILD_RPATH "$<TARGET_FILE_DIR:ppocr_shared>")
  endif()

  add_executable(ppocr_replay examples/ppocr_replay.c)
  target_link_libraries(ppocr_replay PRIVATE $<IF:$<BOOL:${PPOCR_BUILD_SHARED}>,ppocr_shared,ppocr>)
  if(PPOCR_BUILD_SHARED)
    set_target_properties(ppocr_replay PROPERTIES BUILD_RPATH "$<TARGET_FILE_DIR:ppocr_shared>")
  endif()

  add_executable(ppocr_isa_bench examples/ppocr_isa_bench.cpp)
  ppocr_link_objects(ppocr_isa_bench)

  add_executable(ppocr_bench examples/ppocr_bench.cpp)
  ppocr_link_runtime(ppocr_bench)

  add_executable(ppocr_mem_soak examples/ppocr_mem_soak.cpp)
  ppocr_link_runtime(ppocr_mem_soak)
  if(WIN32)
    target_link_libraries(ppocr_mem_soak PRIVATE psapi)
  endif()

  add_executable(ppocr_vulkan_smoke examples/ppocr_vulkan_smoke.cpp)
  ppocr_link_objects(ppocr_vulkan_smoke)

  add_executable(ppocr_metal_check examples/ppocr_metal_check.cpp)
  ppocr_link_objects(ppocr_metal_check)

  add_executable(ppocr_batch_smoke examples/ppocr_batch_smoke.cpp)
  ppocr_link_runtime(ppocr_batch_smoke)

  add_executable(ppocr_kernel_smoke examples/ppocr_kernel_smoke.cpp)
  ppocr_link_objects(ppocr_kernel_smoke)

  add_executable(ppocr_determinism examples/ppocr_determinism.cpp)
  ppocr_link_objects(ppocr_determinism)

  add_executable(ppocr_gpu_graph_smoke examples/ppocr_gpu_graph_smoke.cpp)
  ppocr_link_objects(ppocr_gpu_graph_smoke)

  add_executable(ppocr_gpu_frontend_smoke examples/ppocr_gpu_frontend_smoke.cpp)
  ppocr_link_objects(ppocr_gpu_frontend_smoke)

  add_executable(ppocr_gpu_pipeline_smoke examples/ppocr_gpu_pipeline_smoke.cpp)
  ppocr_link_objects(ppocr_gpu_pipeline_smoke)

  add_executable(ppocr_gpu_ocr_smoke examples/ppocr_gpu_ocr_smoke.cpp)
  ppocr_link_runtime(ppocr_gpu_ocr_smoke)

  add_executable(ppocr_vulkan_robust examples/ppocr_vulkan_robust.cpp)
  ppocr_link_runtime(ppocr_vulkan_robust)

  add_executable(ppocr_gpu_graph_mem_soak examples/ppocr_gpu_graph_mem_soak.cpp)
  ppocr_link_objects(ppocr_gpu_graph_mem_soak)
endif()

if(PPOCR_BUILD_TESTS)
  add_executable(ppocr_c_api_smoke tests/c_api_smoke.c)
  target_link_libraries(ppocr_c_api_smoke PRIVATE $<IF:$<BOOL:${PPOCR_BUILD_SHARED}>,ppocr_shared,ppocr>)
  if(PPOCR_BUILD_SHARED)
    set_target_properties(ppocr_c_api_smoke PROPERTIES BUILD_RPATH "$<TARGET_FILE_DIR:ppocr_shared>")
  endif()
  enable_testing()
  add_test(NAME c_api_smoke COMMAND ppocr_c_api_smoke)
  if(NOT MSVC AND PPOCR_CPU MATCHES "x86_64|amd64|x64|i[3-6]86")
    add_test(NAME isa_encoding
        COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/tests/check_isa_encoding.sh
            ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/ppocr_objects.dir/src/kernels.cpp.o
            ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/ppocr_objects.dir/src/kernels_avx2.cpp.o
            ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/ppocr_objects.dir/src/kernels_avx512.cpp.o)
  endif()
endif()

if(NOT SKBUILD)
  install(TARGETS ${PPOCR_INSTALL_TARGETS}
      EXPORT ppocrTargets
      ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
      LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
      RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
      INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
  install(DIRECTORY include/ppocr DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
  install(EXPORT ppocrTargets
      NAMESPACE ppocr::
      DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ppocr)
  configure_package_config_file(
      cmake/ppocrConfig.cmake.in
      ${CMAKE_CURRENT_BINARY_DIR}/ppocrConfig.cmake
      INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ppocr)
  write_basic_package_version_file(
      ${CMAKE_CURRENT_BINARY_DIR}/ppocrConfigVersion.cmake
      COMPATIBILITY SameMajorVersion)
  install(FILES
      ${CMAKE_CURRENT_BINARY_DIR}/ppocrConfig.cmake
      ${CMAKE_CURRENT_BINARY_DIR}/ppocrConfigVersion.cmake
      DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ppocr)
  install(FILES LICENSE DESTINATION .)
  install(FILES cmake/SDK-README.md DESTINATION . RENAME README.md)
else()
  # scikit-build-core stages the shared library next to the Python package.
  # One unversioned file: wheels cannot carry symlinks, so a versioned
  # library would otherwise ship three full copies.
  set_property(TARGET ppocr_shared PROPERTY VERSION)
  set_property(TARGET ppocr_shared PROPERTY SOVERSION)
  if(NOT PPOCR_BUILD_SHARED)
    message(FATAL_ERROR "The Python wheel needs PPOCR_BUILD_SHARED=ON")
  endif()
  install(TARGETS ppocr_shared
      LIBRARY DESTINATION "${SKBUILD_PLATLIB_DIR}/rapidocr"
      RUNTIME DESTINATION "${SKBUILD_PLATLIB_DIR}/rapidocr"
      ARCHIVE DESTINATION "${SKBUILD_PLATLIB_DIR}/rapidocr")
endif()
