cmake_minimum_required(VERSION 3.10)
set(PSTACK_SOVERSION 2.14)
set(PSTACK_VERSION 2.14)
project(pstack LANGUAGES C CXX VERSION "${PSTACK_VERSION}" )

include (GNUInstallDirs)
enable_testing()
add_subdirectory(tests)

option(PYTHON3 "Compile with python 3 support" OFF)
option(PYTHON2 "Compile with python 2 support" ON)
option(DEBUGINFOD "Compile with debuginfod support" ON)

math(EXPR PLATFORM_BITS "${CMAKE_SIZEOF_VOID_P} * 8")
set(PSTACK_BIN "pstack" CACHE STRING "Name of the 'pstack' binary")

# Generate position independent code, even for static libs - that way we can
# link them to shared libs.
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/" CACHE STRING "Rpath to install for binaries or the empty string")
set(LIBTYPE "SHARED" CACHE STRING "Build libraries as STATIC or SHARED")
option(TIDY "Run clang-tidy on the source" False)

find_library(LTHREADDB NAMES thread_db PATHS (/usr/lib /usr/local/lib))
find_package(LibLZMA)
find_package(ZLIB)

if (PYTHON2)
   find_package(Python2 COMPONENTS Development)
endif()
if (PYTHON3)
   find_package(Python3 COMPONENTS Development)
endif()

find_package(Git)
if (GIT_FOUND AND EXISTS ${CMAKE_SOURCE_DIR}/.git)
   execute_process(
      COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
      OUTPUT_VARIABLE GIT_TAG
      OUTPUT_STRIP_TRAILING_WHITESPACE
      )
   message(STATUS "Version from git tag: ${GIT_TAG}")
else()
   set(GIT_TAG "unknown")
   message(STATUS "git version information unavailable - defaulting to 'unknown'")
endif()

set(VERSION_TAG ${GIT_TAG} CACHE STRING "Version tag (defaults to git commit)")
message(STATUS "Version: ${VERSION_TAG}")

add_definitions(-DVERSION=${VERSION_TAG})

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_EXTENSIONS ON)

add_definitions("-Wall -Wextra -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE")
# Adding frame pointers makes things easy to run performance measurements with,
# and doesn't cost much itself.
add_definitions("-fno-omit-frame-pointer")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Make sure to use the local libpstack headers rather than what's installed.
include_directories(${CMAKE_SOURCE_DIR})

if (LIBLZMA_FOUND)
   set(lzmasrc lzma.cc)
   add_definitions("-DWITH_LZMA")
   include_directories(${LIBLZMA_INCLUDES})
endif()

if (ZLIB_FOUND)
   set(inflatesrc inflate.cc)
   add_definitions("-DWITH_ZLIB")
   include_directories(${ZLIB_INCLUDES})
endif()

if (Python3_Development_FOUND OR Python2_Development_FOUND)
   set(pysrc python.cc)
endif()

if (PYTHON3 AND Python3_Development_FOUND)
   set(pyinc "-I${Python3_INCLUDE_DIRS}")
   message(STATUS  "Python3_INCLUDE_DIRS are ${pyinc}")
   add_definitions("-DWITH_PYTHON3")
   set(pysrc ${pysrc} python3.cc pythonc.c)
   set_source_files_properties(python3.cc PROPERTIES COMPILE_FLAGS ${pyinc})
   set_source_files_properties(pythonc.c PROPERTIES COMPILE_FLAGS ${pyinc})
   set_source_files_properties(canal.cc PROPERTIES COMPILE_FLAGS ${pyinc})
endif()

if (Python2_Development_FOUND)
   set(pysrc ${pysrc} python2.cc)
   add_definitions("-DWITH_PYTHON2")
   set_source_files_properties(python2.cc PROPERTIES COMPILE_FLAGS -I${Python2_INCLUDE_DIRS})
endif()

add_definitions("-g3")

add_library(dwelf ${LIBTYPE}
         dwarf_die.cc
         dwarf_frame.cc
         dwarf_info.cc
         dwarf_lines.cc
         dwarf_macros.cc
         dwarf_pubnames.cc
         dwarf_reader.cc
         dwarf_unit.cc
         dump.cc
         context.cc
         elf.cc
         flags.cc
         reader.cc
         ${inflatesrc}
         ${lzmasrc}
         )

add_library(procman ${LIBTYPE} dead.cc self.cc live.cc process.cc proc_service.cc
    dwarfproc.cc procdump.cc ${stubsrc} ${pysrc})

add_executable(canal canal.cc)

add_executable(${PSTACK_BIN} pstack.cc)

target_link_libraries(procman ${LTHREADDB} dwelf dl)
target_link_libraries(${PSTACK_BIN} dwelf procman)
target_link_libraries(canal dwelf procman)

set_target_properties(dwelf PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION "${PSTACK_SOVERSION}" )
set_target_properties(procman PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION "${PSTACK_SOVERSION}" )

if (ZLIB_FOUND)
   target_link_libraries(dwelf ${ZLIB_LIBRARIES})
else()
   message(WARNING "no ZLIB support found")
endif()

if (LIBLZMA_FOUND)
   target_link_libraries(dwelf ${LIBLZMA_LIBRARIES})
else()
   message(WARNING "no LZMA support found")
endif()

if ((NOT (Python3_Development_FOUND)) AND PYTHON3)
   message(WARNING "no python3 support found")
endif()

if (NOT (Python2_Development_FOUND) AND PYTHON2)
   message(WARNING "no python2 support found")
endif()

if (DEBUGINFOD)
find_path (
   DEBUGINFOD_INCLUDE_DIR NAMES elfutils/debuginfod.h
   HINTS /usr/include/ ${CMAKE_INSTALL_FULL_INCLUDEDIR} )
find_library ( DEBUGINFOD_LIB NAMES debuginfod )

if(DEBUGINFOD_INCLUDE_DIR AND DEBUGINFOD_LIB)
   message(STATUS "found debuginfod header at ${DEBUGINFOD_INCLUDE_DIR}, lib at ${DEBUGINFOD_LIB}")
   target_link_libraries(dwelf ${DEBUGINFOD_LIB})
   add_definitions(-DDEBUGINFOD)
else()
   message(WARNING "no debuginod support found")
endif()
endif()

# bonus: heap debugger
add_library(hdbg SHARED heap.c)
add_executable(hdmp hdmp.cc)
add_executable(stackusers stackusers.cc)
target_link_libraries(hdmp dwelf procman)
target_link_libraries(hdbg dl)
target_link_libraries(stackusers dwelf procman)

install(TARGETS ${PSTACK_BIN} canal)
install(TARGETS hdmp)
install(TARGETS dwelf procman hdbg)
install(FILES ${CMAKE_SOURCE_DIR}/pstack.1 DESTINATION share/man/man1 RENAME ${PSTACK_BIN}.1 )
install(DIRECTORY libpstack DESTINATION include)
install(CODE "execute_process (COMMAND setcap cap_sys_ptrace+ep ${DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/${PSTACK_BIN} RESULT_VARIABLE ret)
if (NOT ret EQUAL \"0\")
   message(\"(setcap failed - you might need to use sudo to use pstack: \${ret})\")
endif()
")

add_test(NAME args COMMAND env PSTACK_BIN=${PSTACK_BIN} ${CMAKE_CURRENT_SOURCE_DIR}/tests/args-test.py)
add_test(NAME badfp COMMAND env PSTACK_BIN=${PSTACK_BIN} ${CMAKE_CURRENT_SOURCE_DIR}/tests/badfp-test.py)
add_test(NAME basic COMMAND env PSTACK_BIN=${PSTACK_BIN} ${CMAKE_SOURCE_DIR}/tests/basic-test.py)
add_test(NAME cpp COMMAND env PSTACK_BIN=${PSTACK_BIN} ${CMAKE_CURRENT_SOURCE_DIR}/tests/cpp-test.py)
add_test(NAME noreturn COMMAND env PSTACK_BIN=${PSTACK_BIN} ${CMAKE_CURRENT_SOURCE_DIR}/tests/noreturn-test.py)
add_test(NAME segv COMMAND env PSTACK_BIN=${PSTACK_BIN} ${CMAKE_SOURCE_DIR}/tests/segv-test.py)
add_test(NAME thread COMMAND env PSTACK_BIN=${PSTACK_BIN} ${CMAKE_CURRENT_SOURCE_DIR}/tests/thread-test.py)
add_test(NAME jsondump COMMAND env PSTACK_BIN=${PSTACK_BIN} ${CMAKE_CURRENT_SOURCE_DIR}/tests/dump-test.py)
add_test(NAME procself COMMAND tests/procself)
if (PYTHON3 AND Python3_Development_FOUND)
   add_test(NAME pydict COMMAND env PSTACK_BIN=${PSTACK_BIN}${CMAKE_CURRENT_SOURCE_DIR}/tests/pydict_test.py)
endif()

# for automake and rpmbuild
add_custom_target(check COMMAND make test)
