# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
#
# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
# property and proprietary rights in and to this material, related
# documentation and any modifications thereto. Any use, reproduction,
# disclosure or distribution of this material and related documentation
# without an express license agreement from NVIDIA CORPORATION or
# its affiliates is strictly prohibited.

cmake_minimum_required(VERSION 3.15 FATAL_ERROR)

project(
  fusa-pdk-samples
  VERSION 2.0.0
  DESCRIPTION "Jetson SIPL Camera Drivers and Sample Applications"
  LANGUAGES CXX
)

set(PDK_HSL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/hsl")
set(PDK_UDDF_DIR "${CMAKE_CURRENT_SOURCE_DIR}/uddf")

if(NOT EXISTS "${PDK_HSL_DIR}")
    message(FATAL_ERROR "HSL directory not found at ${PDK_HSL_DIR}")
endif()

if(NOT EXISTS "${PDK_UDDF_DIR}")
    message(FATAL_ERROR "UDDF directory not found at ${PDK_UDDF_DIR}")
endif()

set(HSL_PYTHON_SCRIPT_DIR "${PDK_HSL_DIR}/pyhsl")
list(APPEND CMAKE_MODULE_PATH "${PDK_HSL_DIR}/integrations/cmake")
include(hsl_compile_rules)

# Define uddf::uddf INTERFACE target providing UDDF public headers
add_library(uddf INTERFACE)
add_library(uddf::uddf ALIAS uddf)
target_compile_features(uddf INTERFACE cxx_std_17)
target_include_directories(uddf INTERFACE
    ${PDK_UDDF_DIR}/include
    ${PDK_HSL_DIR}/include
)

# Set UDDF_CDD_COMMON so sub-CMakeLists can find common helpers via ${UDDF_CDD_COMMON}/helpers
set(UDDF_CDD_COMMON "${CMAKE_CURRENT_SOURCE_DIR}/uddf/common")

# Camera driver include directories (mirror uddf-cdd/ layout exactly):
#   UDDF_CDD_DRIVER   = the drivers/ root so that
#                       #include "serializers/MAX96717/MAX96717.hpp" etc. resolve
#   UDDF_CDD_COSERDES = the coSerDes/ root
set(UDDF_CDD_COMMON_UTILS "${CMAKE_CURRENT_SOURCE_DIR}/uddf/common/utils")
set(UDDF_CDD_DRIVER       "${CMAKE_CURRENT_SOURCE_DIR}/uddf/drivers")
set(UDDF_CDD_COSERDES     "${CMAKE_CURRENT_SOURCE_DIR}/uddf/coSerDes")

# Component drivers are STATIC libraries that get linked into the wrapper
# SHARED libraries (which are already explicitly marked SHARED).  This matches
# the Makefile.tmk build where NV_BUILD_STATIC_LIBRARY is used for drivers
# and NV_BUILD_SHARED_LIBRARY for the library wrappers.
#
# IMPORTANT: BUILD_SHARED_LIBS must be OFF so add_library() without an
# explicit type creates STATIC libraries.  The FORCE ensures any cached
# value from a prior configuration is overwritten.
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Component drivers are STATIC" FORCE)

# PIC is required so that static driver code can be linked into shared wrappers.
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# Hide all symbols from static archives in the final shared libraries.
# Without this, inline HSL bytecode symbols (e.g. max967xx::hsl::check_presence)
# are exported from EVERY wrapper .so.  When the Camera HAL dlopen()s both
# libnvuddf_max96724_library.so and libnvuddf_max96712_library.so, the dynamic
# linker resolves the second library's symbols to the first library's definitions
# (symbol interposition), causing the MAX96712 driver to execute MAX96724's HSL
# sequences.  --exclude-libs,ALL makes all static-archive symbols LOCAL in the
# final .so, so each wrapper keeps its own private copy of the HSL bytecode.
# Crucially, symbols defined in the wrapper's OWN sources (like the extern "C"
# uddf_discover_drivers function) remain exported -- only static-archive symbols
# are affected.
add_link_options("-Wl,--exclude-libs,ALL")

message(STATUS "=== FUSA PDK Samples Configuration ===")
message(STATUS "Project Name:    ${PROJECT_NAME}")
message(STATUS "Project Version: ${PROJECT_VERSION}")
message(STATUS "HSL Directory:   ${PDK_HSL_DIR}")
message(STATUS "UDDF Directory:  ${PDK_UDDF_DIR}")
message(STATUS "Build Type:      ${CMAKE_BUILD_TYPE}")
message(STATUS "=======================================")

# --- Sample drivers (CoE, Eagle) ---
add_subdirectory(uddf/samples/drivers/coe)
add_subdirectory(uddf/samples/drivers/eagle)

# --- SIPL sample apps (independent of driver builds) ---
# These link against pre-installed SIPL shared libraries on the target device.
# On a host without those libraries, they are gracefully skipped.
# Each sample's CMakeLists.txt defines its own install() rules.
# Run `sudo make install` from build/ to install all targets.
#
# samples/camera is added later in this file (after the hololink probe) so
# it can link against the hololink_core static library for HSB metadata
# parsing. samples/query has no such dependency and stays here.
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/samples/query")
    add_subdirectory(samples/query)
endif()

# --- Deserializer drivers ---
add_subdirectory(uddf/drivers/deserializers/MAX96712)
add_subdirectory(uddf/drivers/deserializers/MAX96724)

# --- Serializer drivers ---
add_subdirectory(uddf/drivers/serializers/MAX96717)
add_subdirectory(uddf/drivers/serializers/MAX9295)

# --- Sensor drivers ---
add_subdirectory(uddf/drivers/sensors/IMX728)
add_subdirectory(uddf/drivers/sensors/IMX623)
add_subdirectory(uddf/drivers/sensors/AR0234)

# --- EEPROM drivers ---
add_subdirectory(uddf/drivers/eeproms/GT24C64D)
add_subdirectory(uddf/drivers/eeproms/BR24H128)

# --- Power controller drivers ---
add_subdirectory(uddf/drivers/powerControllers/MAX20087)

# --- Platform power management driver ---
add_subdirectory(uddf/drivers/tegra-deser-power)

# --- Hololink core (static, PIC) required by eagle_library wrapper ---
# Provides the `hololink_core` static archive that the Eagle UDDF library
# wrapper links into its SHARED .so. Built before the libraries/ subdir
# below so the target is defined when uddf/libraries/moduleEagle adds it
# to its target_link_libraries().
#
# HOLOLINK_AVAILABLE gates the Eagle library wrapper. When hololink is
# skipped (missing CUDA or {fmt} headers, or sources not present), the
# eagle_library SHARED .so is also skipped.
set(HOLOLINK_AVAILABLE FALSE)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/hololink/src/hololink/core/CMakeLists.txt")
    # CUDA headers are a required dependency for hololink_core.  Do not
    # accept a partial CUDA runtime header staging area here: hololink includes
    # the CUDA driver API header directly via <cuda.h>.
    set(HOLOLINK_REQUIRED_CUDA_HEADERS
        cuda.h
        cuda_runtime.h
        vector_types.h
        crt/host_config.h
        crt/host_defines.h
        crt/device_functions.h
        crt/common_functions.h
    )
    set(CUDA_HEADERS_SEARCH_PATHS
        "/usr/local/cuda-13.2/include"
        "/usr/local/cuda-13.2/targets/sbsa-linux/include"
        "/usr/local/cuda-13.2/targets/aarch64-linux/include"
        "/usr/local/cuda/include"
        "/usr/include/cuda-13.2"
    )
    if(CUDAToolkit_ROOT)
        list(APPEND CUDA_HEADERS_SEARCH_PATHS
            "${CUDAToolkit_ROOT}/include"
            "${CUDAToolkit_ROOT}/targets/sbsa-linux/include"
            "${CUDAToolkit_ROOT}/targets/aarch64-linux/include"
        )
    endif()
    if(DEFINED ENV{CUDA_HOME})
        list(APPEND CUDA_HEADERS_SEARCH_PATHS "$ENV{CUDA_HOME}/include")
    endif()
    if(DEFINED ENV{CUDA_PATH})
        list(APPEND CUDA_HEADERS_SEARCH_PATHS "$ENV{CUDA_PATH}/include")
    endif()
    list(REMOVE_DUPLICATES CUDA_HEADERS_SEARCH_PATHS)

    set(CUDA_HEADERS_DIR "")
    foreach(_cuda_path IN LISTS CUDA_HEADERS_SEARCH_PATHS)
        if(NOT _cuda_path)
            continue()
        endif()

        set(_cuda_path_has_required_headers TRUE)
        foreach(_cuda_header IN LISTS HOLOLINK_REQUIRED_CUDA_HEADERS)
            if(NOT EXISTS "${_cuda_path}/${_cuda_header}")
                set(_cuda_path_has_required_headers FALSE)
                break()
            endif()
        endforeach()

        if(_cuda_path_has_required_headers)
            set(CUDA_HEADERS_DIR "${_cuda_path}")
            break()
        endif()
    endforeach()

    if(NOT CUDA_HEADERS_DIR)
        message(WARNING
            "Required CUDA headers (${HOLOLINK_REQUIRED_CUDA_HEADERS}) not found "
            "in any of: ${CUDA_HEADERS_SEARCH_PATHS}. "
            "Skipping hololink build. Install the CUDA 13.2 toolkit, or stage "
            "the full CUDA headers, including cuda.h, under /usr/include/cuda-13.2 "
            "to enable hololink.")
        set(CUDA_INCLUDE_DIR "" CACHE PATH
            "CUDA include directory used by UDDF drivers (e.g., hsb)" FORCE)
        set(CUDAToolkit_INCLUDE_DIR "" CACHE PATH "CUDA Toolkit include dir" FORCE)
    else()
        # Locate {fmt} library headers. Prefer the CMake config package
        # (provided by libfmt-dev), then fall back to a header-only check
        # via find_path so we can also accept a manually extracted copy.
        find_package(fmt QUIET)
        if(NOT fmt_FOUND)
            find_path(FMT_INCLUDE_DIR
                NAMES fmt/format.h
                PATHS /usr/include /usr/local/include
            )
        endif()

        if(NOT fmt_FOUND AND NOT FMT_INCLUDE_DIR)
            message(WARNING
                "{fmt} library headers not found. Skipping hololink build. "
                "Install libfmt-dev (e.g. `sudo apt install libfmt-dev`) to "
                "enable hololink.")
        else()
            set(CUDAToolkit_ROOT "/usr/local/cuda-13.2" CACHE PATH "CUDA Toolkit root")
            set(CUDAToolkit_INCLUDE_DIR "${CUDA_HEADERS_DIR}" CACHE PATH "CUDA Toolkit include dir" FORCE)
            # Reuse the exact directory that passed the required-header probe so
            # cached values cannot point hsb/hololink at a partial CUDA install.
            if(EXISTS "${CUDA_HEADERS_DIR}")
                set(CUDA_INCLUDE_DIR "${CUDA_HEADERS_DIR}"
                    CACHE PATH "CUDA include directory used by UDDF drivers (e.g., hsb)" FORCE)
            else()
                set(CUDA_INCLUDE_DIR ""
                    CACHE PATH "CUDA include directory used by UDDF drivers (e.g., hsb)" FORCE)
            endif()
            message(STATUS "Hololink: using CUDA headers from ${CUDA_HEADERS_DIR}")
            message(STATUS "UDDF drivers: using CUDA includes from ${CUDA_INCLUDE_DIR}")
            if(fmt_FOUND)
                message(STATUS "Hololink: using {fmt} CMake package")
            else()
                message(STATUS "Hololink: using {fmt} headers from ${FMT_INCLUDE_DIR}")
            endif()

            add_subdirectory(hololink/src/hololink/core)
            set(HOLOLINK_AVAILABLE TRUE)
        endif()
    endif()
endif()

# --- nvsipl_camera sample app ---
# Added after the hololink probe so HsbTransportMetadata.cpp can link against
# the hololink_core static target when HOLOLINK_AVAILABLE is true.
if(HOLOLINK_AVAILABLE AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/samples/camera/CMakeLists.txt")
    add_subdirectory(samples/camera)
else()
    message(STATUS "nvsipl_camera: Skipping build because hololink is unavailable")
endif()

# --- Eagle component drivers ---
# These feed the Eagle UDDF library wrapper, which also links hololink_core.
# Keep them behind the same dependency gate so a system without CUDA headers
# can still build the non-Eagle drivers and samples.
if(HOLOLINK_AVAILABLE)
    add_subdirectory(uddf/drivers/hsb)
    add_subdirectory(uddf/drivers/vb1940)
else()
    message(STATUS "Eagle: skipping HSB transport and VB1940 drivers because hololink is unavailable")
endif()

# --- coSerDes module drivers ---
add_subdirectory(uddf/coSerDes/modules/AR0234HAWK)
add_subdirectory(uddf/coSerDes/modules/R0SIM623)
add_subdirectory(uddf/coSerDes/modules/R0SIM728)

# Library wrappers: each implements uddf_discover_drivers() and links the component
# drivers above into the final libnvuddf_*_library.so files used by the SIPL runtime.
add_subdirectory(uddf/libraries)

# Rename output .so files to nvuddf_* to match the names expected by the SIPL runtime.
if(HOLOLINK_AVAILABLE)
    set_target_properties(eagle_library                PROPERTIES OUTPUT_NAME nvuddf_eagle_library)
endif()
set_target_properties(uddf_max96712_library            PROPERTIES OUTPUT_NAME nvuddf_max96712_library)
set_target_properties(uddf_max96724_library            PROPERTIES OUTPUT_NAME nvuddf_max96724_library)
set_target_properties(uddf_ar0234hawkcameramodule_library  PROPERTIES OUTPUT_NAME nvuddf_ar0234hawkcameramodule_library)
set_target_properties(uddf_r0sim623cameramodule_library    PROPERTIES OUTPUT_NAME nvuddf_r0sim623cameramodule_library)
set_target_properties(uddf_r0sim728cameramodule_library    PROPERTIES OUTPUT_NAME nvuddf_r0sim728cameramodule_library)
set_target_properties(max20087_library                     PROPERTIES OUTPUT_NAME nvuddf_max20087_library)
set_target_properties(uddf_tegra_deser_power_library       PROPERTIES OUTPUT_NAME nvuddf_tegra_deser_power_library)

set(_all_driver_targets
    # Library wrappers (the installable .so files)
    uddf_max96712_library uddf_max96724_library
    uddf_ar0234hawkcameramodule_library
    uddf_r0sim623cameramodule_library uddf_r0sim728cameramodule_library
    max20087_library
    uddf_tegra_deser_power_library
    # Sample drivers (always built)
    sample_coe_driver
    eagle_driver
)
if(HOLOLINK_AVAILABLE)
    list(APPEND _all_driver_targets eagle_library)
endif()

add_custom_target(all_drivers ALL
    DEPENDS ${_all_driver_targets}
    COMMENT "Building all FUSA PDK driver library wrappers"
)

# Install destination for the Camera HAL driver catalog.
# Must match the path that CameraHalPortal scans at runtime.
set(NVSIPL_DRV_INSTALL_DIR /usr/lib/nvsipl_drv)

# Install the top-level library wrappers (libnvuddf_*_library.so).
# These are the files that the Camera HAL discovers and dlopen()s.
set(_install_library_targets
    uddf_max96712_library
    uddf_max96724_library
    uddf_ar0234hawkcameramodule_library
    uddf_r0sim623cameramodule_library
    uddf_r0sim728cameramodule_library
    max20087_library
    uddf_tegra_deser_power_library
)
if(HOLOLINK_AVAILABLE)
    list(APPEND _install_library_targets eagle_library)
endif()

install(TARGETS ${_install_library_targets}
    LIBRARY DESTINATION ${NVSIPL_DRV_INSTALL_DIR}
    COMPONENT drivers
)

# Component drivers are now static libraries linked into the wrappers above,
# so there are no separate .so files to install for them.

# --- Install summary (run `sudo make install` from build/) ---
#   /usr/sbin/nvsipl_camera              camera streaming app        (samples/camera/)
#   /usr/sbin/nvsipl_query          platform config query tool  (samples/query/)
#   /usr/lib/nvsipl_drv/libnvuddf_*.so   UDDF driver libraries       (uddf/libraries/)
