cmake_minimum_required(VERSION 3.11.4) # for RHEL 8

project(
  linyaps-box
  VERSION 2.1.3
  DESCRIPTION "A simple OCI runtime for desktop applications"
  HOMEPAGE_URL "https://github.com/OpenAtom-Linyaps/linyaps-box"
  LANGUAGES CXX)

# ==============================================================================
# Utilities
# ==============================================================================

# NOTE: Modified from https://www.scivision.dev/cmake-project-is-top-level/
if(CMAKE_VERSION VERSION_LESS 3.21)
  get_property(
    not_top
    DIRECTORY
    PROPERTY PARENT_DIRECTORY)
  if(NOT not_top)
    set(linyaps-box_IS_TOP_LEVEL true)
  else()
    set(linyaps-box_IS_TOP_LEVEL false)
  endif()
endif()

# ==============================================================================
# Build options
# ==============================================================================

option(linyaps-box_STATIC "Build linyaps-box as statically-linked binary." OFF)

option(linyaps-box_ENABLE_SECCOMP "Build linyaps-box with seccomp support." OFF)

option(linyaps-box_ENABLE_CAP "Build linyaps-box with capability support" ON)

option(linyaps-box_ENABLE_UNIT_TESTS "Enable unit tests."
       ${linyaps-box_IS_TOP_LEVEL})

option(linyaps-box_ENABLE_SMOKE_TESTS "Enable smoke tests." OFF)

option(linyaps-box_MAKE_RELEASE "Make release build." OFF)

if(linyaps-box_ENABLE_SMOKE_TESTS OR linyaps-box_ENABLE_UNIT_TESTS)
  set(linyaps-box_ENABLE_TESTING ON)
endif()

option(linyaps-box_ENABLE_COVERAGE "Enable coverage." OFF)

if(linyaps-box_ENABLE_COVERAGE AND NOT linyaps-box_ENABLE_TESTING)
  message(
    FATAL_ERROR
      "linyaps-box_ENABLE_COVERAGE requires linyaps-box_ENABLE_UNIT_TESTS or linyaps-box_ENABLE_SMOKE_TESTS."
  )
endif()

option(linyaps-box_ENABLE_CPACK "Enable CPack." OFF)

if(linyaps-box_ENABLE_CPACK)
  set(linyaps-box_CPACK_PACKAGING_INSTALL_PREFIX
      "/opt/org.openatom.linyaps-box"
      CACHE STRING "Install prefix for package generated by CPack.")
endif()

set(linyaps-box_CLONE_CHILD_STACK_SIZE
    "(1U << 20)"
    CACHE
      STRING
      "DO NOT MODIFY THIS SETTINGS UNLESS YOU KNOW WHAT YOU ARE DOING. Size of child stack in bytes when using clone(2) to create container."
)

set(linyaps-box_STACK_GROWTH_DOWN
    true
    CACHE
      BOOL
      "DO NOT MODIFY THIS SETTINGS UNLESS YOU KNOW WHAT YOU ARE DOING. If stacks grow upward on your processor, set this to false."
)

set(linyaps-box_DEFAULT_LOG_LEVEL
    LOG_WARNING
    CACHE
      STRING
      "The default syslog priority. This is used to filter log messages at runtime time."
)

set(linyaps-box_ACTIVE_LOG_LEVEL
    LOG_WARNING
    CACHE
      STRING
      "The active syslog priority. This is used to filter log messages at compile time."
)

set(linyaps-box_ENABLE_CPM
    OFF
    CACHE BOOL "enable CPM")

set(linyaps-box_HARDENING
    ON
    CACHE BOOL "enable hardening")

set(CMAKE_POSITION_INDEPENDENT_CODE ON)

if(CMAKE_VERSION VERSION_LESS "3.14")
  set(linyaps-box_ENABLE_CPM OFF)
  message(
    STATUS "cmake version ${CMAKE_VERSION} not compatible with CPM.cmake.")
endif()

# if just want to try local packages at first set CPM_USE_LOCAL_PACKAGES ON
set(linyaps-box_CPM_LOCAL_PACKAGES_ONLY
    OFF
    CACHE BOOL "use local packages only")

# ==============================================================================

if(linyaps-box_HARDENING)
  message(STATUS "applying harden settings")
  set(CMAKE_EXE_LINKER_FLAGS
      "${CMAKE_EXE_LINKER_FLAGS} -pie -Wl,-z,relro -Wl,-z,now")
  add_compile_options(-fstack-protector-strong)

  if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
    message(STATUS "Non-Debug build, enabling FORTIFY_SOURCE")
    add_compile_definitions(_FORTIFY_SOURCE=2)
  endif()
endif()

set(linyaps-box_LIBRARY linyaps-box)
set(linyaps-box_LIBRARY_SOURCE
    # find -regex '\./src/.+\.[ch]\(pp\)?' -type f -printf '%P\n'| sort
    src/linyaps_box/app.cpp
    src/linyaps_box/app.h
    src/linyaps_box/cgroup.h
    src/linyaps_box/cgroup_manager.cpp
    src/linyaps_box/cgroup_manager.h
    src/linyaps_box/command/exec.cpp
    src/linyaps_box/command/exec.h
    src/linyaps_box/command/kill.cpp
    src/linyaps_box/command/kill.h
    src/linyaps_box/command/list.cpp
    src/linyaps_box/command/list.h
    src/linyaps_box/command/options.cpp
    src/linyaps_box/command/options.h
    src/linyaps_box/command/run.cpp
    src/linyaps_box/command/run.h
    src/linyaps_box/config.cpp
    src/linyaps_box/config.h
    src/linyaps_box/container.cpp
    src/linyaps_box/container.h
    src/linyaps_box/container_monitor.cpp
    src/linyaps_box/container_monitor.h
    src/linyaps_box/container_ref.cpp
    src/linyaps_box/container_ref.h
    src/linyaps_box/container_status.cpp
     src/linyaps_box/container_status.h
     src/linyaps_box/impl/disabled_cgroup_manager.cpp
     src/linyaps_box/impl/disabled_cgroup_manager.h
     src/linyaps_box/impl/json_printer.cpp
     src/linyaps_box/impl/json_printer.h
     src/linyaps_box/impl/table_printer.cpp
     src/linyaps_box/impl/table_printer.h
    src/linyaps_box/interface.cpp
    src/linyaps_box/interface.h
    src/linyaps_box/io/epoll.cpp
    src/linyaps_box/io/epoll.h
    src/linyaps_box/io/forwarder.cpp
    src/linyaps_box/io/forwarder.h
    src/linyaps_box/printer.cpp
    src/linyaps_box/printer.h
    src/linyaps_box/runtime.cpp
    src/linyaps_box/runtime.h
    src/linyaps_box/socket.cpp
    src/linyaps_box/socket.h
     src/linyaps_box/status_directory.cpp
     src/linyaps_box/status_directory.h
     src/linyaps_box/status_directory_manager.cpp
     src/linyaps_box/status_directory_manager.h
    src/linyaps_box/terminal.cpp
    src/linyaps_box/terminal.h
    src/linyaps_box/unix_socket.cpp
    src/linyaps_box/unix_socket.h
    src/linyaps_box/utils/atomic_write.cpp
    src/linyaps_box/utils/atomic_write.h
    src/linyaps_box/utils/cgroups.cpp
    src/linyaps_box/utils/cgroups.h
    src/linyaps_box/utils/close_range.cpp
    src/linyaps_box/utils/close_range.h
    src/linyaps_box/utils/defer.h
    src/linyaps_box/utils/epoll.cpp
    src/linyaps_box/utils/epoll.h
    src/linyaps_box/utils/file.cpp
    src/linyaps_box/utils/file_describer.cpp
    src/linyaps_box/utils/file_describer.h
    src/linyaps_box/utils/file.h
    src/linyaps_box/utils/inspect.cpp
    src/linyaps_box/utils/inspect.h
    src/linyaps_box/utils/ioctl.h
    src/linyaps_box/utils/log.cpp
    src/linyaps_box/utils/log.h
    src/linyaps_box/utils/mkdir.cpp
    src/linyaps_box/utils/mkdir.h
    src/linyaps_box/utils/mknod.cpp
    src/linyaps_box/utils/mknod.h
    src/linyaps_box/utils/mman.cpp
    src/linyaps_box/utils/platform.cpp
    src/linyaps_box/utils/platform.h
    src/linyaps_box/utils/process.cpp
    src/linyaps_box/utils/process.h
    src/linyaps_box/utils/ringbuffer.cpp
    src/linyaps_box/utils/ringbuffer.h
    src/linyaps_box/utils/semver.cpp
    src/linyaps_box/utils/semver.h
    src/linyaps_box/utils/session.cpp
    src/linyaps_box/utils/session.h
    src/linyaps_box/utils/signal.cpp
    src/linyaps_box/utils/signal.h
    src/linyaps_box/utils/span.h
    src/linyaps_box/utils/symlink.cpp
    src/linyaps_box/utils/symlink.h
    src/linyaps_box/utils/terminal.cpp
    src/linyaps_box/utils/terminal.h
    src/linyaps_box/utils/utils.h)

set(LINYAPS_BOX_VERSION ${PROJECT_VERSION})

if(NOT linyaps-box_MAKE_RELEASE)
  message(STATUS "make dev build")

  execute_process(
    COMMAND git rev-parse --short=7 HEAD
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    OUTPUT_VARIABLE GIT_COMMIT_HASH
    OUTPUT_STRIP_TRAILING_WHITESPACE)

  set(LINYAPS_BOX_VERSION "${LINYAPS_BOX_VERSION}-dev-${GIT_COMMIT_HASH}")
endif()

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/linyaps_box/version.h.in
               ${CMAKE_CURRENT_BINARY_DIR}/src/linyaps_box/version.h @ONLY)

# for gcc 8
set(linyaps-box_LIBRARY_LINK_LIBRARIES "stdc++fs")
set(linyaps-box_LIBRARY_INCLUDE_DIRS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src"
                                     "${CMAKE_CURRENT_BINARY_DIR}/src")

if(linyaps-box_STATIC)
  set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
endif()

find_package(PkgConfig REQUIRED)

if(linyaps-box_ENABLE_SECCOMP)
  pkg_check_modules(libseccomp REQUIRED IMPORTED_TARGET libseccomp>=2.3.3)
  list(APPEND linyaps-box_LIBRARY_LINK_LIBRARIES PUBLIC PkgConfig::libseccomp)
endif()

if(linyaps-box_ENABLE_CAP)
  pkg_check_modules(libcap REQUIRED IMPORTED_TARGET libcap>=2.25)
  list(APPEND linyaps-box_LIBRARY_LINK_LIBRARIES PUBLIC PkgConfig::libcap)
endif()

if(linyaps-box_ENABLE_CPM)
  list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
  include(CPM)

  if(linyaps-box_CPM_LOCAL_PACKAGES_ONLY)
    set(CPM_USE_LOCAL_PACKAGES ON)
  endif()

  CPMFindPackage(
    NAME nlohmann_json
    VERSION 3.11.3
    GITHUB_REPOSITORY nlohmann/json
    GIT_TAG v3.12.0
    EXCLUDE_FROM_ALL ON
    OPTIONS "JSON_BuildTests OFF")
  CPMFindPackage(
    NAME CLI11
    VERSION 2.5.0
    GITHUB_REPOSITORY CLIUtils/CLI11
    GIT_TAG v2.6.2
    EXCLUDE_FROM_ALL ON
    OPTIONS "CLI11_BUILD_TESTS OFF")
endif()

# find minimal version
find_package(nlohmann_json 3.11.3 QUIET)
if(NOT nlohmann_json_FOUND)
  add_subdirectory(external/nlohmann_json)
  list(APPEND CMAKE_MODULE_PATH
       "${CMAKE_CURRENT_SOURCE_DIR}/cmake.external/nlohmann_json")
  find_package(nlohmann_json 3.11.3 REQUIRED)
  message(STATUS "use vendor nlohmann_json ${nlohmann_json_VERSION}")
endif()

list(APPEND linyaps-box_LIBRARY_LINK_LIBRARIES PUBLIC
     nlohmann_json::nlohmann_json)

find_package(CLI11 2.5.0 QUIET)
if(NOT CLI11_FOUND)
  add_subdirectory(external/CLI11)
  list(APPEND CMAKE_MODULE_PATH
       "${CMAKE_CURRENT_SOURCE_DIR}/cmake.external/CLI11")
  find_package(CLI11 2.5.0 REQUIRED)
  message(STATUS "use vendor CLI11 ${CLI11_VERSION}")
endif()

list(APPEND linyaps-box_LIBRARY_LINK_LIBRARIES PUBLIC CLI11::CLI11)

add_library("${linyaps-box_LIBRARY}" STATIC ${linyaps-box_LIBRARY_SOURCE})
target_include_directories("${linyaps-box_LIBRARY}"
                           ${linyaps-box_LIBRARY_INCLUDE_DIRS})
target_link_libraries("${linyaps-box_LIBRARY}"
                      PRIVATE ${linyaps-box_LIBRARY_LINK_LIBRARIES})
target_compile_features("${linyaps-box_LIBRARY}" PUBLIC cxx_std_17)
set_property(TARGET "${linyaps-box_LIBRARY}" PROPERTY CXX_STANDARD 17)
set_property(TARGET "${linyaps-box_LIBRARY}" PROPERTY CXX_EXTENSIONS OFF)
set_property(TARGET "${linyaps-box_LIBRARY}" PROPERTY CXX_STANDARD_REQUIRED ON)

include(CheckIncludeFileCXX)
check_include_file_cxx("linux/openat2.h" LINYAPS_BOX_HAVE_OPENAT2_H)

if(${LINYAPS_BOX_HAVE_OPENAT2_H})
  target_compile_definitions("${linyaps-box_LIBRARY}"
                             PRIVATE "LINYAPS_BOX_HAVE_OPENAT2_H")
endif()

target_compile_definitions(
  "${linyaps-box_LIBRARY}"
  PRIVATE
    "LINYAPS_BOX_CLONE_CHILD_STACK_SIZE=${linyaps-box_CLONE_CHILD_STACK_SIZE}"
  PRIVATE "LINYAPS_BOX_STACK_GROWTH_DOWN=${linyaps-box_STACK_GROWTH_DOWN}"
  PRIVATE "LINYAPS_BOX_DEFAULT_LOG_LEVEL=${linyaps-box_DEFAULT_LOG_LEVEL}"
  PRIVATE "LINYAPS_BOX_ACTIVE_LOG_LEVEL=${linyaps-box_ACTIVE_LOG_LEVEL}")
target_compile_options("${linyaps-box_LIBRARY}"
                       PRIVATE -fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.)
if(linyaps-box_STATIC)
  target_compile_definitions("${linyaps-box_LIBRARY}"
                             PUBLIC "LINYAPS_BOX_STATIC_LINK")
endif()

if(linyaps-box_ENABLE_SECCOMP)
  target_compile_definitions("${linyaps-box_LIBRARY}"
                             PUBLIC LINYAPS_BOX_ENABLE_SECCOMP)
endif()

if(linyaps-box_ENABLE_CAP)
  target_compile_definitions("${linyaps-box_LIBRARY}"
                             PUBLIC LINYAPS_BOX_ENABLE_CAP)
endif()

if(linyaps-box_ENABLE_COVERAGE)
  if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    target_compile_options("${linyaps-box_LIBRARY}"
                           PRIVATE -fprofile-instr-generate -fcoverage-mapping)
    target_link_options("${linyaps-box_LIBRARY}" PUBLIC
                        -fprofile-instr-generate -fcoverage-mapping)
  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    target_compile_options("${linyaps-box_LIBRARY}" PRIVATE --coverage)
    target_link_options("${linyaps-box_LIBRARY}" PUBLIC --coverage)
  else()
    message(
      FATAL_ERROR "Coverage is not supported for ${CMAKE_CXX_COMPILER_ID}.")
  endif()
endif()

# ==============================================================================

set(linyaps-box_APP ll-box)
set(linyaps-box_APP_SOURCE "./app/${linyaps-box_APP}/src/main.cpp")
set(linyaps-box_APP_LINK_LIBRARIES PRIVATE "${linyaps-box_LIBRARY}")
set(linyaps-box_APP_SOURCE_INCLUDE_DIRS
    PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/app/${linyaps-box_APP}/src")

add_executable("${linyaps-box_APP}" ${linyaps-box_APP_SOURCE})
target_include_directories("${linyaps-box_APP}"
                           ${linyaps-box_APP_SOURCE_INCLUDE_DIRS})
target_link_libraries("${linyaps-box_APP}" ${linyaps-box_APP_LINK_LIBRARIES})
if(linyaps-box_STATIC)
  target_link_options("${linyaps-box_APP}" PRIVATE -static -static-libgcc
                      -static-libstdc++)
endif()
target_compile_features("${linyaps-box_APP}" PRIVATE cxx_std_17)
set_property(TARGET "${linyaps-box_APP}" PROPERTY CXX_STANDARD 17)
set_property(TARGET "${linyaps-box_APP}" PROPERTY CXX_EXTENSIONS OFF)
set_property(TARGET "${linyaps-box_APP}" PROPERTY CXX_STANDARD_REQUIRED ON)
target_compile_definitions(
  "${linyaps-box_APP}"
  PRIVATE "LINYAPS_BOX_DEFAULT_LOG_LEVEL=${linyaps-box_DEFAULT_LOG_LEVEL}"
  PRIVATE "LINYAPS_BOX_ACTIVE_LOG_LEVEL=${linyaps-box_AVTIVE_LOG_LEVEL}")
target_compile_options("${linyaps-box_APP}"
                       PRIVATE -fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.)

# ==============================================================================

include(GNUInstallDirs)

# parameter TYPE was added in CMake 3.14
if(CMAKE_VERSION VERSION_LESS "3.14")
  install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/${linyaps-box_APP}"
          DESTINATION "${CMAKE_INSTALL_BINDIR}")
else()
  install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/${linyaps-box_APP}" TYPE BIN)
endif()

if(linyaps-box_ENABLE_CPACK)
  set(CPACK_PACKAGING_INSTALL_PREFIX
      "${linyaps-box_CPACK_PACKAGING_INSTALL_PREFIX}")
  set(CPACK_PACKAGE_CONTACT "chenlinxuan@uniontech.com")
  set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
  set(CPACK_RPM_PACKAGE_AUTOREQ ON)
  include(CPack)
endif()

# ==============================================================================

if(NOT linyaps-box_ENABLE_TESTING)
  return()
endif()

enable_testing()

add_subdirectory(tests)
