# Builds the vendored standalone GGUF quantizer (vendor/quantizer, extracted
# from ./quantizer — no ggml dependency) as a shared library and installs it
# into the gguf_editor package so the Python ctypes bindings can load it.
# Modeled on llama-cpp-python's CMakeLists.txt.
cmake_minimum_required(VERSION 3.15)
project(gguf_editor C CXX)

option(GGUF_EDITOR_BUILD "Build the quantizer shared library and install it alongside the python package" ON)
option(QUANTIZER_CUDA  "build with CUDA support (NVIDIA GPUs)" OFF)
option(QUANTIZER_HIP   "build with HIP/ROCm support (AMD GPUs)" OFF)
option(QUANTIZER_METAL "build with Metal support (Apple GPUs)" OFF)

if(NOT GGUF_EDITOR_BUILD)
    return()
endif()

if(QUANTIZER_CUDA AND QUANTIZER_HIP)
    message(FATAL_ERROR "QUANTIZER_CUDA and QUANTIZER_HIP are mutually exclusive")
endif()

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD 11)

if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
    set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()

# Statically link the MSVC CRT so the wheel works on machines without the
# VC++ redistributable (same as the standalone quantizer build).
if(MSVC)
    set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>" CACHE STRING "" FORCE)
endif()

set(QZ_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/quantizer)

set(QUANTIZER_SOURCES
    ${QZ_DIR}/src/quantize.cpp
    ${QZ_DIR}/src/quantize.h
    ${QZ_DIR}/src/gguf_io.cpp
    ${QZ_DIR}/src/gguf_io.h
    ${QZ_DIR}/src/safetensors_io.cpp
    ${QZ_DIR}/src/safetensors_io.h
    ${QZ_DIR}/src/device.cpp
    ${QZ_DIR}/src/device.h
    ${QZ_DIR}/src/kernels/ggml-quants.c   # vendored from ggml (verbatim)
    ${QZ_DIR}/src/kernels/qz-traits.c
    ${QZ_DIR}/src/kernels/qz-init.cpp
)

if(QUANTIZER_CUDA)
    enable_language(CUDA)
    if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
        set(CMAKE_CUDA_ARCHITECTURES "75;80;86;87;89;90;100;120")
    endif()
    list(APPEND QUANTIZER_SOURCES ${QZ_DIR}/src/device_gpu.cu ${QZ_DIR}/src/device_gpu.h)
endif()

if(QUANTIZER_HIP)
    enable_language(HIP)
    list(APPEND QUANTIZER_SOURCES ${QZ_DIR}/src/device_gpu.cu ${QZ_DIR}/src/device_gpu.h)
    set_source_files_properties(${QZ_DIR}/src/device_gpu.cu PROPERTIES LANGUAGE HIP)
endif()

if(QUANTIZER_METAL)
    if(NOT APPLE)
        message(FATAL_ERROR "QUANTIZER_METAL requires macOS")
    endif()
    enable_language(OBJCXX)
    list(APPEND QUANTIZER_SOURCES ${QZ_DIR}/src/device_metal.mm ${QZ_DIR}/src/device_metal.h)
    set_source_files_properties(${QZ_DIR}/src/device_metal.mm PROPERTIES COMPILE_OPTIONS "-fobjc-arc")
endif()

add_library(gguf_quantizer SHARED ${QUANTIZER_SOURCES})
target_include_directories(gguf_quantizer PUBLIC ${QZ_DIR}/src)
target_compile_definitions(gguf_quantizer PRIVATE GGUF_QUANTIZER_BUILD PUBLIC GGUF_QUANTIZER_SHARED)

if(QUANTIZER_CUDA)
    find_package(CUDAToolkit REQUIRED)
    target_compile_definitions(gguf_quantizer PRIVATE QZ_USE_CUDA)
    target_link_libraries(gguf_quantizer PRIVATE CUDA::cudart)
endif()

if(QUANTIZER_HIP)
    find_package(hip REQUIRED)
    target_compile_definitions(gguf_quantizer PRIVATE QZ_USE_HIP)
    target_link_libraries(gguf_quantizer PRIVATE hip::host)
endif()

if(QUANTIZER_METAL)
    target_compile_definitions(gguf_quantizer PRIVATE QZ_USE_METAL)
    find_library(FOUNDATION_FRAMEWORK Foundation REQUIRED)
    find_library(METAL_FRAMEWORK Metal REQUIRED)
    target_link_libraries(gguf_quantizer PRIVATE ${FOUNDATION_FRAMEWORK} ${METAL_FRAMEWORK})
endif()

find_package(Threads REQUIRED)
target_link_libraries(gguf_quantizer PRIVATE Threads::Threads)

set_target_properties(gguf_quantizer PROPERTIES
    CXX_VISIBILITY_PRESET hidden
    C_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN ON
)

if(NOT MSVC)
    target_compile_options(gguf_quantizer PRIVATE
        $<$<COMPILE_LANGUAGE:C>:-O3>
        $<$<COMPILE_LANGUAGE:CXX>:-O3>
    )
endif()

if(MINGW)
    # -static-libgcc/-static-libstdc++ alone still leave a dynamic dependency
    # on libwinpthread-1.dll, which is not shipped in the wheel; -static folds
    # it (and any other MinGW runtime) into the DLL.
    target_link_options(gguf_quantizer PRIVATE -static -static-libgcc -static-libstdc++)
elseif(UNIX AND NOT APPLE)
    target_link_options(gguf_quantizer PRIVATE -static-libgcc -static-libstdc++)
endif()

if(WIN32 AND TARGET gguf_quantizer)
    set_target_properties(gguf_quantizer PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()

# Keep any shipped runtime deps resolvable next to the library.
if(APPLE)
    set_target_properties(gguf_quantizer PROPERTIES
        INSTALL_RPATH "@loader_path" BUILD_WITH_INSTALL_RPATH TRUE)
elseif(UNIX)
    set_target_properties(gguf_quantizer PROPERTIES
        INSTALL_RPATH "$ORIGIN" BUILD_WITH_INSTALL_RPATH TRUE)
endif()

# Install into the wheel (SKBUILD_PLATLIB_DIR) and also into the source tree
# so editable installs (`pip install -e .`) find the library — same dual
# install llama-cpp-python uses.
install(
    TARGETS gguf_quantizer
    LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/src/gguf_editor/lib
    RUNTIME DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/src/gguf_editor/lib
    ARCHIVE DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/src/gguf_editor/lib
)
if(DEFINED SKBUILD_PLATLIB_DIR)
    install(
        TARGETS gguf_quantizer
        LIBRARY DESTINATION ${SKBUILD_PLATLIB_DIR}/gguf_editor/lib
        RUNTIME DESTINATION ${SKBUILD_PLATLIB_DIR}/gguf_editor/lib
        ARCHIVE DESTINATION ${SKBUILD_PLATLIB_DIR}/gguf_editor/lib
    )
endif()
